memo
For more detailed information about React concepts and APIs, please refer to the official documentation for memo.
React.memo lets you optimize components by memoizing their output. This prevents unnecessary rerenders when the props haven't changed, improving performance for pure components.
Without memoization, a component will rerender whenever its parent rerenders, even if its props remain the same. By wrapping a component with React.memo, React will skip rendering the component and reuse the last rendered output if the props are unchanged.
ReactMemoComponentAttribute
The [<ReactMemoComponent>] attribute provides a convenient way to memoize functional components in Feliz. It does the transformation on transpile level so you don't have to wrap your component manually.
[<ReactMemoComponent>] // memoizes component to prevent rerender whenever parent rerenders
let ChildComponent (onClick: unit -> unit) =
// ... component implementation
Check out the ReactMemoComponentAttribute documentation for more details.
Check the output in the browser console
The child component below is memoized using the [<ReactMemoComponent>] attribute. It only rerenders when its props change.
Show code
module Example.MemoAttribute
open Feliz
open Browser.Dom
[<ReactMemoComponent>]
let RenderTextWithEffect (text: string) =
React.useEffect (fun () -> console.log("Rerender!", text) )
Html.div [
prop.text text;
prop.testId "memo-attribute"
]
[<ReactComponent(true)>]
let Main () =
let isDark, setIsDark = React.useState(false)
let text, setText = React.useState("Hello, world!")
let fgColor = if isDark then color.white else color.black
let bgColor = if isDark then color.black else color.white
Html.div [
prop.style [style.border(1, borderStyle.solid, fgColor); style.padding 20; style.color fgColor; style.backgroundColor bgColor]
prop.children [
Html.h3 "Check the output in the browser console"
Html.p "The child component below is memoized using the [<ReactMemoComponent>] attribute. It only rerenders when its props change."
Html.button [
prop.text "Toggle Dark Mode"
prop.onClick (fun _ -> setIsDark(not isDark))
]
Html.input [
prop.value text
prop.onChange setText
]
RenderTextWithEffect(text)
]
]
React.memo function
This approach has several limitations compared to using the [<ReactMemoComponent>] attribute (see example below):
- You must use a
letbinding for your component, to ensure Fable transpiling asconst. - You must use any F# type transpiling to a JavaScript object for props (e.g., anonymous record,
[<PojoAttribute>]).
let MemoizedComponent =
React.memo<{|text: string|}> (fun props ->
// ... component implementation
)
Check the output in the browser console
Show code
module Example.MemoFunction
open Feliz
open Browser.Dom
[<ReactComponent>]
let RenderTextWithEffect (text: string) =
React.useEffect (fun () -> console.log("Rerender!", text) )
Html.div [
prop.text text;
prop.testId "memo-attribute"
]
let MemoFunction =
React.memo<{|text: string|}> (fun props ->
RenderTextWithEffect(props.text)
)
[<ReactComponent(true)>]
let Main () =
let isDark, setIsDark = React.useState(false)
let text, setText = React.useState("Hello, world!")
let fgColor = if isDark then color.white else color.black
let bgColor = if isDark then color.black else color.white
Html.div [
prop.style [style.border(1, borderStyle.solid, fgColor); style.padding 20; style.color fgColor; style.backgroundColor bgColor]
prop.children [
Html.h3 "Check the output in the browser console"
Html.button [
prop.text "Toggle Dark Mode"
prop.onClick (fun _ -> setIsDark(not isDark))
]
Html.input [
prop.value text
prop.onChange setText
]
React.memoRender(MemoFunction, {| text = text |})
]
]
areEqual
The React.memo function also accepts an optional second argument, areEqual, which is a custom comparison function for props. This function receives the previous and next props and should return true if they are equal (i.e., no rerender needed) or false if they are different (i.e., rerender needed).
This is useful as memo only does a shallow comparison of props by default. If your props are complex objects or arrays, you may need to provide a custom comparison function to accurately determine equality.
Check the output in the browser console
Show code
module Example.MemoFunctionAreEqual
open Feliz
open Browser.Dom
[<ReactComponent>]
let RenderArrayWithEffect (fruitArray: string []) =
React.useEffect (fun () -> console.log("Rerender!", fruitArray) )
Html.div [
prop.text (String.concat ", " fruitArray);
prop.testId "memo-attribute"
]
let MemoFunction =
React.memo<{|fruitArray: string[]|}> (
(fun props ->
RenderArrayWithEffect(props.fruitArray)),
(fun prevProps nextProps ->
prevProps.fruitArray.Length = nextProps.fruitArray.Length &&
prevProps.fruitArray |> Array.forall2 (=) nextProps.fruitArray
)
)
[<ReactComponent(true)>]
let Main () =
let isDark, setIsDark = React.useState(false)
let fruits = [| "Apple"; "Banana"; "Orange" |]
let fgColor = if isDark then color.white else color.black
let bgColor = if isDark then color.black else color.white
Html.div [
prop.style [style.border(1, borderStyle.solid, fgColor); style.padding 20; style.color fgColor; style.backgroundColor bgColor]
prop.children [
Html.h3 "Check the output in the browser console"
Html.button [
prop.text "Toggle Dark Mode"
prop.onClick (fun _ -> setIsDark(not isDark))
]
React.memoRender(MemoFunction, {| fruitArray = fruits |})
]
]