React useMemo Deep Dive
Table of Contents + −
In the last lesson, React React.memo, you saw that fresh objects break memo. This lesson goes deeper into useMemo and its second job: keeping a value’s reference stable so React.memo and effects don’t fire needlessly.
🤔 The two jobs of useMemo, revisited
useMemo really does two things, and the second one is the performance star.
- Skip expensive work: it caches a slow calculation so it only re-runs when its dependencies change. So this is about saving CPU time.
- Keep a reference stable: it returns the same object or array between renders, so things comparing by reference don’t see a change. So this one is about identity, and that’s what makes
React.memoand effect dependencies behave.
🔗 What is reference equality?
This is the key idea, so let’s nail it down. In JavaScript, objects are compared by reference, not by their contents.
const a = { name: "Alex" };const b = { name: "Alex" };
console.log(a === b); // false! different objects, even with same contentsconsole.log(a === a); // true, same objectRead what’s happening.
aandbhold objects that look identical, but see, they’re two separate objects in memory.===on objects asks “are these the exact same object?”, not “do they contain the same thing?”.- So
a === bisfalse, even though their contents match. - And here’s the connection to rendering: every time a component renders, any object written inside it is a brand new object. So it’s never
===to the one from last render.
This is why memo breaks with objects
A { ... } literal in your component is a new object on every render. So a child wrapped in React.memo that receives it always sees a “different” prop, and re-renders anyway. The contents are the same, but the reference isn’t.
🧩 useMemo keeps the reference stable
useMemo solves the identity problem: it returns the same object across renders until a dependency changes.
import { useMemo } from "react";
function Parent({ userId }) { // ✅ same object identity unless userId changes const user = useMemo(() => ({ id: userId, role: "admin" }), [userId]);
return <Child user={user} />;}Walk through what this buys you.
- Without
useMemo,{ id: userId, role: "admin" }would be a new object every render. - With it, React returns the same object until
userIdchanges. - So if
Childis wrapped inReact.memo, it now sees the sameuserprop and skips re-rendering. - Then when
userIddoes change,useMemomakes a new object, andChildre-renders correctly.
💡 The full picture: memo + useMemo together
Let’s see the team in action: a memoized child plus a memoized object prop means the child truly skips needless renders.
import { useState, useMemo, memo } from "react";
const Child = memo(function Child({ config }) { console.log("Child rendered"); return <p>Mode: {config.mode}</p>;});
function Parent() { const [count, setCount] = useState(0);
// stable object: same reference every render const config = useMemo(() => ({ mode: "dark" }), []);
return ( <div> <button onClick={() => setCount(count + 1)}>Count: {count}</button> <Child config={config} /> </div> );}Here’s why the child stays quiet.
configis memoized with[], so it’s the same object on every render.- Clicking the button re-renders
Parent, butconfigkeeps its identity. Childis wrapped inmemo, sees the sameconfig, and skips its render.- Remove the
useMemoandconfigwould be new each render, soChildwould re-render on every click.
Output
Child rendered(click) ... Child skipped(click) ... Child skipped⚖️ useMemo has a cost
Here’s the deep part people miss: useMemo is not free.
- React has to remember the dependencies and the saved value, and compare them each render.
- For cheap calculations, that bookkeeping can cost more than just redoing the work.
- So
useMemois a trade: you pay a little overhead to save a bigger cost. - And if the cost you’re saving is tiny,
useMemois a net loss in both speed and readability. - So only reach for it when the thing you’re saving is genuinely expensive, or when you need a stable reference for
memoor an effect.
Don't memo everything
Wrapping every value in useMemo makes code harder to read and can be slower, not faster. It pays off in two clear cases: a genuinely heavy calculation, or keeping an object/function reference stable for React.memo or an effect dependency. Otherwise, skip it.
⚠️ Common Mistakes
A common mistake is using useMemo for a cheap value, expecting magic speed.
// ❌ trivial work - the memo overhead costs more than just computing itconst total = useMemo(() => price + tax, [price, tax]);
// ✅ just compute it directlyconst total = price + tax;Keep these in mind.
- Don’t memoize cheap calculations. Plain code is faster and clearer.
- Don’t forget dependencies, or you get a stale cached value.
- Don’t expect
useMemoto help amemochild unless the memoized value is actually passed to it as a prop.
✅ Best Practices
A few habits for useMemo in performance work.
- Use it for genuinely expensive calculations, or to keep an object/array reference stable.
- Pair it with
React.memo: memoize the object prop, and the memoized child can skip renders. - Always list every dependency the calculation reads.
- Skip it for cheap values, and measure before adding it.
Identity is the real reason here
In performance work, you’ll reach for useMemo more often for reference stability than for slow math. Keeping an object the same between renders is what makes React.memo and effect dependencies behave. That’s the deep takeaway.
🧩 What You’ve Learned
- ✅
useMemohas two jobs: cache expensive work, and keep a value’s reference stable - ✅ Objects are compared by reference (
===), not contents, so a new object each render looks “changed” - ✅ A fresh object prop defeats
React.memo;useMemokeeps it stable somemoworks - ✅ Together, a memoized object prop and a
memochild stop needless re-renders - ✅
useMemohas real overhead, so it pays off only for heavy work or reference stability - ✅ Always list dependencies, and skip
useMemofor cheap values
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
How does JavaScript compare two objects with ===?
Why: === on objects checks identity, not contents. Two objects with identical contents are still not equal because they are different objects in memory.
- 2
Why does a fresh object prop defeat React.memo?
Why: memo compares props with ===. A new object literal each render has a new reference, so it always looks changed and the child re-renders anyway.
- 3
What does useMemo do for an object you pass to a memo child?
Why: useMemo returns the same object until a dependency changes. That stable reference lets a React.memo child see an unchanged prop and skip the render.
- 4
When is useMemo NOT worth it?
Why: useMemo has its own overhead. For trivial work it's a net loss. Use it for heavy work or reference stability, not for simple, fast calculations.
🚀 What’s Next?
Now you understand reference stability for values. Functions have the exact same problem, a new function every render breaks memo too. Next you’ll go deeper into useCallback, the tool for keeping function props stable.