useMemo and useCallback — Caching Between Renders
Your reading list grew to 5,000 books, and each render sorts them all by title — even when the only thing that changed was the dark-mode toggle. Rendering is usually fast enough not to care, but when one calculation is genuinely slow, React lets you keep the last answer until its inputs change.
The rule
Cache the result; recompute only when a dependency changes.
useMemo caches a value.
import { useMemo } from 'react';
function BookList({ books, theme }) {
const sorted = useMemo(
() => [...books].sort((a, b) => a.title.localeCompare(b.title)),
[books]
);
return <ul className={theme}>{sorted.map(b => <li key={b.id}>{b.title}</li>)}</ul>;
}On the first render React runs the function and stores the result. On later renders it compares each dependency with the previous render's; if none changed, it hands back the stored result without calling the function. When theme changes, books is the same array, so the sort is skipped. (Note the [...books] copy: sort mutates, and props must not be changed.)
useCallback caches a function.
const handleRemove = useCallback(
id => dispatch({ type: 'removed', id }),
[]
);Every render normally creates brand-new function objects. That matters when a child is wrapped in memo (which skips re-rendering if its props are unchanged) or when a function is an effect dependency — a new function each time looks like a change. useCallback(fn, deps) is the same as useMemo(() => fn, deps): it keeps the same function until a dependency changes.
Performance, not correctness. React may occasionally throw a cache away, so your code must still work if the value is recomputed. And caching has its own cost: comparing dependencies and holding memory. The newer React Compiler can add this memoisation automatically, so many projects need fewer hand-written calls.
The shortcut
Before memoising, ask: "Is it slow, or does someone compare it?"
- Slow calculation that repeats with the same inputs →
useMemo. - Function passed to a
memochild or listed in an effect's dependencies →useCallback. - Neither → write plain code.
First render
useMemoruns the sort on 5,000 books and stores the result with[books].
Drills
Answer fast — trust the rule.
What does useMemo(() => compute(a), [a]) return on a render where a has not changed?
Which is equivalent to useCallback(fn, [x])?
A memo-wrapped BookCard still re-renders on every parent render. Its props are a book object and onRemove={() => remove(id)}. What is the likely cause?
Is it worth wrapping const full = first + ' ' + last in useMemo?
Remember
- Cache the result; recompute only when a dependency changes.
- Slow, or compared? Only then reach for
useMemooruseCallback. - Trap: treating memoisation as required for correctness, or sprinkling it everywhere.
