24
loading...
This website collects cookies to deliver better user experience
React.memo
or useMemo
.import React, { useState } from "react";
// [App.js]
function App() {
const [name, setName] = useState('');
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<SlowSubtree />
</div>
);
}
function SlowSubtree() {
sleep(500); // try increasing time here 💣
return <p>Artifically Slow subtree</p>;
}
// [utils.js]
function sleep(time) {
const exitAt = Date.now() + time;
while (exitAt > Date.now()) {
// simulate expensive subtree
}
}
<SlowSubtree />
component. Which gets re-rendered on a change in input. As we type in input we set the name
state thus causing a re-render of App
component, which then renders <SlowSubtree />
. The quick fix here is wrapping SlowSubtree
component in a React.memo
as shown below:import React, { memo } from "react";
// [App.js]
function SlowSubtreeComponent() {
sleep(500);
return <p>Artifically Slow subtree</p>
}
const SlowSubtree = memo(SlowSubtreeComponent);
React.memo()
is a quick fix but can we avoid using it? Following are some approaches:name
state with every keystroke and causing re-rendering each time input changes, setting the state on every keystroke is wastage here. What we can do is we can debounce set state calls to prevent rendering with each keystroke. I consider this as a bit of a hacky approach but I’ve put this here to bring this to your notice.import React, {
useState,
useMemo,
useLayoutEffect,
useRef
} from "react";
// [App.js]
function App() {
const [name, setName] = useState('');
const debounceOnChange = useDebounceFn(
(e) => setName(e.target.value)
);
return (
<div>
<input
type="text"
onChange={debounceOnChange} />
<SlowSubtree />
</div>
);
}
function SlowSubtree() {
sleep(500); // try increasing time here 💣
return <p>Artifically Slow subtree</p>;
}
// [utils.js]
function sleep(time) {
const exitAt = Date.now() + time;
while (exitAt > Date.now()) {
// simulate expensive subtree
}
}
// [hooks.js]
import debounce from "lodash.debounce";
function useDebounceFn(callbackFn, delay = 500) {
const callbackFnRef = useRef(callbackFn);
useLayoutEffect(() => {
callbackFnRef.current = callbackFn;
});
return useMemo(
() =>
debounce((...args) =>callbackFnRef.current(...args), delay),
[delay]
);
}
SlowSubtree
the component renders because of a state change in the parent component. The changing part here is name
state with <input/>
while SlowSubtree
is not changing. We can split and move state down in its separate component like shown below:import React, { useState } from "react";
// [App.js]
function App() {
const [name, setName] = useState('');
return (
<div>
<NameInput />
<SlowSubtree />
</div>
);
}
function NameInput() {
const [name, setName] = useState('');
return (
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}
function SlowSubtree() {
sleep(500); // try increasing time here 💣
return <p>Artifically Slow subtree</p>;
}
// [utils.js]
function sleep(time) {
const exitAt = Date.now() + time;
while (exitAt > Date.now()) {
// simulate expensive subtree
}
}
NameInput
component we can also move the state up and leverage a pattern called render as child.props.children
instead. Here we will lift the state up in its own component and wrap SlowSubtree
component with it.import React, { useState } from "react";
// [App.js]
function App() {
return (
<NameComponent>
<SlowSubtree />
</NameComponent>
);
}
function NameComponent(props) {
const [name, setName] = useState('');
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
{props.children}
</div>
);
}
function SlowSubtree() {
sleep(500); // try increasing time here 💣
return <p>Artifically Slow subtree</p>;
}
// [utils.js]
function sleep(time) {
const exitAt = Date.now() + time;
while (exitAt > Date.now()) {
// simulate expensive subtree
}
}
name
state changed NameComponent
re-render but as it still gets the same children
prop as last time so React doesn’t need to visit SlowSubtree
subtree. And as a result <SlowSubtree />
doesn’t re-render.React.memo()
or useMemo()
again you should stop and think to see if you can split part that changes from the parts that don’t change. So take into account if state relocation can help before you settle with the memo.