Cache an Expensive Computation Until Its Inputs Change
useMemo
`useMemo` remembers the result of a computation and skips re-doing it on later renders if the inputs haven't changed.
What you'll learn
- Memoize a heavy computation
- Memoize an object/array to keep its reference stable
- Recognize when memoization is NOT worth it
useMemo(fn, deps) runs fn, remembers its return value, and only
re-runs fn when one of deps changes. Same shape as useEffect,
but it produces a value instead of running a side effect.
Memoizing Heavy Work
function Search({ items, query }) {
const filtered = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
}, [items, query]);
return <List items={filtered} />;
} Without useMemo, the filter runs every render. With it, the
filter runs only when items or query change.
Stabilizing Object/Array References
The second use case: keep a value’s identity stable across renders so a downstream comparison (memo, effect deps) doesn’t see it as new.
function Page({ id }) {
// ✗ New object every render — Child sees a "different" prop every time
return <Child config={{ id, mode: "live" }} />;
}
function Page({ id }) {
// ✓ Same object until id changes
const config = useMemo(() => ({ id, mode: "live" }), [id]);
return <Child config={config} />;
} This usually only matters when Child is wrapped in memo (see
the Patterns chapter) or compares its props somewhere expensive.
Deps Follow The Same Rules As useEffect
Everything reactive that fn reads should be in deps. The
ESLint rule will tell you if you forget.
const greeting = useMemo(() => `Hi, ${name}!`, [name]); When Memoization Is Worth It
- The computation is genuinely slow (sorting/filtering many thousands of items, complex regex, JSON parsing)
- A child wrapped in
memoshould skip when only unrelated state changes in the parent - An effect or another memo lists this value in deps and you don’t want it to re-run constantly
When it’s NOT worth it:
- Most light computations (
a + b, simple.map) - Tiny arrays
- “Just in case” — the overhead can exceed the savings
The Mental Model
useMemo is a cache keyed by deps. Same deps → same cached
result. Different deps → recompute.
Up Next
Same idea for functions: keep them from being created brand-new on every render.
useCallback →