useMemo and useCallback
When memoization helps — referential equality, expensive pure work, memo children, dependency arrays, and the cost of caching everything.
- react
- usememo
- usecallback
- performance
- memo
useMemo and useCallback cache a value or a function identity between renders. They do not make React “faster” by default. They fix specific problems: expensive pure calculations you do not want to redo, and unstable props that break memo / dependency arrays.
If you wrap every expression in useMemo, you pay hook overhead, dependency bookkeeping, and cognitive load — often for zero user-visible win. Measure, then memoize the edges that matter.
Reconcile first: Reconciliation. Hooks order still applies: Rules of Hooks.
The problem they solve
Every render of a function component runs the body again. That creates new object and function identities even when contents are logically the same:
function Parent({ items }) {
const [q, setQ] = useState('');
const filters = { q }; // new object every render
const onSelect = (id) => console.log(id); // new function every render
return <ItemList items={items} filters={filters} onSelect={onSelect} />;
}
If ItemList is wrapped in React.memo, it still re-renders whenever Parent does — filters and onSelect failed shallow compare. useMemo / useCallback stabilize those references when dependencies haven’t changed.
Model
const memoizedValue = useMemo(() => computeExpensive(a, b), [a, b]);
const memoizedFn = useCallback((...args) => doThing(a, ...args), [a]);
| Hook | Caches | Recomputes when |
|---|---|---|
useMemo(() => value, deps) |
Return value of the factory | Any dep changes (Object.is) |
useCallback(fn, deps) |
The function itself | Any dep changes |
useCallback(fn, deps) is equivalent to useMemo(() => fn, deps) for function identity. Prefer useCallback for readability when the product is a callback.
Important: the factory for useMemo should be pure. Do not fetch, subscribe, or write refs for side effects inside it — use useEffect / event handlers.
Example: expensive pure derivation
function ProductGrid({ products, sortKey, hideOutOfStock }) {
const visible = useMemo(() => {
let list = products;
if (hideOutOfStock) list = list.filter((p) => p.stock > 0);
return [...list].sort((a, b) => compare(a, b, sortKey));
}, [products, sortKey, hideOutOfStock]);
return visible.map((p) => <Card key={p.id} product={p} />);
}
Worth it when products is large and the parent re-renders often for unrelated state (theme toggle, open dropdown). Not worth it for ten items and a cheap filter.
If the parent only re-renders when products / filters change anyway, useMemo adds nothing.
Example: stable callback for memo child
const Row = memo(function Row({ item, onToggle }) {
// expensive row chrome…
return (
<button type="button" onClick={() => onToggle(item.id)}>
{item.label}
</button>
);
});
function List({ items }) {
const [selected, setSelected] = useState(() => new Set());
const onToggle = useCallback((id) => {
setSelected((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
}, []); // setSelected is stable
return items.map((item) => (
<Row key={item.id} item={item} onToggle={onToggle} />
));
}
Without useCallback, every parent render gives every Row a new onToggle → all memoized rows re-render. With a stable callback, only rows whose item prop changed re-render (assuming item identities are stable).
List identity still needs correct keys.
Example: dependency arrays and effects
function Search({ query }) {
const params = useMemo(() => ({ q: query, limit: 20 }), [query]);
useEffect(() => {
fetch(`/api?${new URLSearchParams(params)}`)
.then(/* … */);
}, [params]);
}
Here useMemo keeps params referentially stable when query is unchanged so the effect does not re-fire spuriously. Alternative: depend on query directly and build params inside the effect — often clearer:
useEffect(() => {
const params = { q: query, limit: 20 };
// fetch…
}, [query]);
Do not invent memo layers to silence exhaustive-deps without understanding the data flow.
What useMemo does not do
- Does not skip the component render by itself — the component still ran to call
useMemo. - Does not deep-compare props for children — that’s
memo+ stable props. - Does not replace state — cached values are not reactive sources.
- Does not guarantee cache hits across remounts — remount resets hooks.
- Is not free — stores deps, compares them, retains the value (memory).
React docs increasingly frame memoization as a tool for semantic stability and measured performance, not a default coding style.
Composition: memo + useCallback is a system
Parent state update
→ Parent body runs
→ Child receives props
→ If Child is memoized AND every prop is Object.is-equal to last time
→ Child render skipped
Unstable props break the chain at the last step. Fix one of:
- Stabilize the prop (
useMemo/useCallback/ hoist constants) - Move state down so Parent doesn’t re-render
- Pass
children/ composition so the expensive subtree isn’t recreated by the parent’s other state - Accept the re-render if it’s cheap
Composition often beats blanket memoization — pass children that already rendered higher up.
Anti-patterns
// Useless: primitive already compared by value
const n = useMemo(() => count + 1, [count]);
// Harmful noise: tiny object, child not memoized
const style = useMemo(() => ({ color: 'red' }), []);
// Broken: missing deps — stale closure
const onClick = useCallback(() => {
console.log(value);
}, []); // value frozen forever
Always list every reactive value from component scope that the callback/factory reads — same discipline as useEffect.
Compiler note (mental model for 2025+)
React’s compiler can auto-memoize in some setups. Until your repo relies on it, hand-written useMemo/useCallback remain the portable control surface. Don’t delete measured memoization because “the compiler will save us” unless the compiler is actually enabled and verified in your pipeline.
Footguns
- Memoizing without a memoized consumer or expensive cost — pure ceremony.
- Unstable deps (
useMemo(() => f(obj), [obj])whereobjis new every time). - Putting non-pure work in
useMemo. - Over-stabilizing so children never update when you forgot a dep.
- Using
useCallbackto “optimize” a button that isn’t heavy — noise in review. - New array method results as deps —
items.filter(...)inline in the dep array creates a new array every render; memoize that result or depend onitemsand filter inside.
Interview angle
Prompt: “Difference between useMemo and useCallback? When do you use them?”
Strong answer: “useMemo caches a computed value; useCallback caches a function reference. Both recompute when dependencies change. I use them when referential equality matters — memoized children, effect deps, or expensive pure derivations — after identifying a real cost. They don’t stop the parent from rendering; they make certain values stable across renders.”
Follow-up demo: show a memo child breaking on inline onClick={() => …} and fixed with useCallback.
Related on this site
- Reconciliation — when re-renders happen
- React.memo — consumer of stable props
- Keys in Lists — list identity before memo games
- useEffect Fundamentals — dependency discipline
- Rules of Hooks — call order
- Context Performance Pitfalls — value identity problems at scale
Further reading
Related guides
- Reconciliation & the Virtual DOMWhat re-render actually means, why keys exist, and when memo is a tool — not a personality trait.
- Accessibility Patterns in ReactPractical React a11y: labels, focus management, keyboard, live regions, and composition patterns that stay accessible.
- Avoid Prop Drilling with CompositionStop threading props through intermediates: children slots, inversion of control, and when context is the right escape hatch.
- Batching State UpdatesHow React 18+ batches setState in events, timeouts, and promises: when updates flush and why double setState still works.
- Children Prop PatternsUsing children and slot props for flexible APIs: wrappers, compound components, and when to prefer explicit props.