🎓 What You Will Learn
- What useMemo does: skip recomputing a value when its inputs haven't changed
- Expensive calculations: memoizing on the real dependency, not every render
- Stabilizing references: making objects/arrays keep the same identity across renders
- useMemo + React.memo: why they only work together
- useMemo vs useCallback: memoizing a value vs. memoizing a function
- When NOT to use it: the overhead cases where memoizing makes things worse
What useMemo Actually Does
Every time a component re-renders, every line inside its body runs again — including any calculation you wrote directly in the render. For a cheap calculation that's invisible. For an expensive one (a big loop, sorting a large array, transforming a large dataset), doing it on every render — even ones triggered by an unrelated state change — is wasted work.
useMemo(calculateValue, dependencies) caches the result and only reruns calculateValue when something in dependencies actually changed.
Memoizing an Expensive Calculation
The classic case: a component has state that's unrelated to the expensive calculation, but updating that unrelated state still re-renders the whole component — and without useMemo, that means redoing the expensive work for no reason.
Primes below 50,000: 5,133
Actually recomputed 1 time(s) — clicking "Unrelated re-render" does NOT bump this number.
useMemo doesn't make the calculation itself faster — it just skips running it again when nothing it depends on has changed.Stabilizing Object & Array References
The second big use case has nothing to do with slow calculations. Every { ... } or [...] you write inline creates a brand-new object each render — even if its contents are identical to last time. That matters a lot once that object is passed as a prop to a React.memo'd child.
Child rendered 1 time(s)
theme: dark, size: 16
Toggle the second button, then click "Re-render parent" a few times and watch whether the child's render count keeps climbing.
React.memo alone isn't enough. It does a shallow prop comparison — but a new object is never === to the previous one, even with identical fields. You need useMemo upstream to give it a stable reference to compare against.useMemo vs. useCallback
These two solve the exact same problem for two different kinds of values. useMemo memoizes the result of calling a function. useCallback memoizes the function itself — it's really just useMemo under the hood.
// useCallback(fn, deps) is exactly equivalent to:
const memoizedFn = useMemo(() => fn, deps);
// Use useMemo when you want to cache a VALUE:
const sortedList = useMemo(() => [...list].sort(), [list]);
// Use useCallback when you want to cache a FUNCTION:
const handleClick = useCallback(() => doSomething(id), [id]);| Hook | Memoizes | Typical use |
|---|---|---|
| useMemo | A computed value | Expensive calculations, stable object/array props |
| useCallback | A function reference | Stable event handlers passed to memoized children |
When NOT to Reach for useMemo
useMemo itself isn't free — it costs a dependency comparison and a bit of memory on every render. For a calculation that's already cheap (string concatenation, a simple sum, formatting a date), that overhead can outweigh the tiny amount of work you're saving.
useMemo when you've noticed an actual slowdown (or you know a calculation is expensive up front), and when a value feeds either a dependency array or a React.memo'd child. Wrapping every single value "just in case" adds noise without adding speed.Common Mistakes
// ❌ BAD: the overhead of useMemo exceeds the cost of the calculation itself
const fullName = useMemo(() => `${first} ${last}`, [first, last]);
// ✅ GOOD: just compute it directly — it's already fast
const fullName = `${first} ${last}`;// ❌ BAD: "tax" changes but isn't in the dependency array —
// total goes stale and silently shows an outdated value
const total = useMemo(() => price * (1 + tax), [price]);
// ✅ GOOD: every value the calculation reads belongs in the array
const total = useMemo(() => price * (1 + tax), [price, tax]);// ❌ Pointless: Child isn't wrapped in memo(), so it re-renders
// regardless of whether "config" has a stable reference or not
function Child({ config }) { return <div>{config.theme}</div>; }
// ✅ GOOD: memo() + useMemo() work together
const Child = memo(function Child({ config }) {
return <div>{config.theme}</div>;
});
const config = useMemo(() => ({ theme: "dark" }), []);Best Practices
Pattern Reference
| Situation | Do this |
|---|---|
| A calculation is measurably slow (big loop, large sort/filter) | useMemo(() => compute(), [deps]) |
| An object/array prop feeds a React.memo'd child | useMemo(() => ({ ... }), [deps]) |
| A cheap calculation (string concat, small math) | Just compute it directly — no useMemo |
| You need to memoize a function, not a value | useCallback instead |
Practice Project
Build a small product table that:
- Filters and sorts a list of 1,000+ products based on a search box and a sort toggle
- Memoizes the filtered + sorted result with
useMemo, depending on the search term and sort order - Renders each row as a
React.memo'd component that receives a memoized style object - Adds an unrelated counter elsewhere on the page and confirms clicking it doesn't re-run the filter/sort
What's Next?
- React.memo: the component-level counterpart that useMemo often pairs with
- useCallback: revisit memoizing functions instead of values
- Code Splitting: the other major lever for performance — reducing what loads at all
- Custom Hooks: wrap memoized calculations into a reusable hook
Keep practicing! 💪 useMemo is a scalpel, not a default — reach for it when you can point at a real slowdown or a real reference-equality problem, and skip it everywhere else.