React

useMemo — Memoizing Expensive Values in React

Thirdy Gayares
12 min read

🎓 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
1

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.

2

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.

The dependency array is the whole point. useMemo doesn't make the calculation itself faster — it just skips running it again when nothing it depends on has changed.
3

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.
4

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.

usememo-vs-usecallback.jsx
// 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]);
HookMemoizesTypical use
useMemoA computed valueExpensive calculations, stable object/array props
useCallbackA function referenceStable event handlers passed to memoized children
5

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.

Don't memoize by default. Reach for 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.
6

Common Mistakes

1Memoizing a cheap calculation
mistake-cheap.jsx
// ❌ 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}`;
2Forgetting a dependency
mistake-missing-dep.jsx
// ❌ 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]);
3Expecting useMemo without React.memo to help
mistake-no-memo-child.jsx
// ❌ 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" }), []);
7

Best Practices

Measure before memoizing — profile first, don't guess where the slowdown is
Include every value the calculation reads in the dependency array
Pair with React.memo when stabilizing a prop, not on its own
Skip it for cheap calculations — string formatting, simple math, small arrays
Use useCallback for functions, useMemo for everything else
8

Pattern Reference

SituationDo this
A calculation is measurably slow (big loop, large sort/filter)useMemo(() => compute(), [deps])
An object/array prop feeds a React.memo'd childuseMemo(() => ({ ... }), [deps])
A cheap calculation (string concat, small math)Just compute it directly — no useMemo
You need to memoize a function, not a valueuseCallback instead
9

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
10

What's Next?

🚀 Next Learning Topics:
  • 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.

About the Author

TG

Thirdy Gayares

Passionate developer creating custom solutions for everyone. I specialize in building user-friendly tools that solve real-world problems while maintaining the highest standards of security and privacy.