Performance (memo/useMemo/useCallback)
When and why to optimize React renders - React.memo, useMemo, useCallback, and the golden rule: measure before optimizing.
What it is, in plain English: React re-renders are usually cheap, so most components need no optimization at all. When a specific component IS measurably slow (expensive calculations, large lists), three tools help: React.memo skips re-rendering a component if its props haven't changed. useMemo caches the result of an expensive calculation between renders. useCallback caches a function reference so it doesn't change on every render -- important when passing callbacks to memoized children.
Most components don't need optimization
The golden rule: measure first. React's default re-rendering is fast.
Adding memo/useMemo/useCallback everywhere "just in case" adds complexity and can
actually make things SLOWER (memoization itself has a cost). Only optimize a component
after you've measured it's actually slow.
React.memo -- skip re-rendering if props are unchanged
// Without memo: ExpensiveChild re-renders every time Parent re-renders,
// even if 'data' never changed:
function ExpensiveChild({ data }) {
console.log("Rendering..."); // logs every time Parent re-renders
return <div>{data}</div>;
}
// With memo: only re-renders if 'data' actually changed:
const ExpensiveChild = React.memo(function ExpensiveChild({ data }) {
console.log("Rendering..."); // only logs when 'data' actually changes
return <div>{data}</div>;
});
function Parent() {
const [unrelatedState, setUnrelatedState] = React.useState(0);
return (
<div>
<button onClick={() => setUnrelatedState(s => s + 1)}>Click</button>
<ExpensiveChild data="constant" />
{/* memo() skips re-rendering ExpensiveChild — its props never change */}
</div>
);
}useMemo -- cache an expensive calculation's result
function ProductList({ products }) {
// Without useMemo: this runs on EVERY render, even unrelated ones:
const sorted = products.slice().sort((a, b) => b.price - a.price);
// With useMemo: only re-sorts when 'products' actually changes:
const sortedMemo = React.useMemo(() => {
return products.slice().sort((a, b) => b.price - a.price);
}, [products]);
return <ul>{sortedMemo.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
// Use useMemo for calculations that are genuinely expensive --
// sorting/filtering large arrays, complex math. NOT for simple operations.useCallback -- cache a function reference
function Parent() {
// Without useCallback: a NEW function is created every render:
const handleClick = () => console.log("clicked");
// With useCallback: the SAME function reference, unless deps change:
const handleClickMemo = React.useCallback(() => {
console.log("clicked");
}, []); // never changes
return <MemoizedChild onClick={handleClickMemo} />;
// Why this matters: MemoizedChild is React.memo'd, but a new onClick
// function every render would defeat that memo (new reference = "changed prop")
}
Try It Yourself (JSX)
Official Sources
- React Documentation — React.memo Reference
- React Documentation — useMemo Reference
- React Documentation — useCallback Reference
- React Documentation — React Compiler
- react-window Documentation
The Codex links only to official documentation. No third-party tutorials.