useMemo feels like it should cache "the same value," but React does not compare business meaning. It compares dependency identity. If an array or object is recreated on every render, useMemo sees a new dependency and recomputes anyway.
That is why so many memoization attempts do nothing measurable.
The Common Mistake
This looks correct:
const sorted = useMemo(() => expensiveSort(items), [items]);
But if the parent rebuilds items each time:
<Dashboard items={[...rawItems]} />
the memo never really sticks. The child is correct. The input is unstable.
Where useMemo Actually Pays Off
Memoization tends to be worth it when one of these is true:
- the derived computation is expensive
- the value is passed into a memoized subtree
- referential stability affects downstream rendering behavior
It is usually not worth it for tiny computations that exist only to make the code feel optimized.
The Better Mental Model
Good memoization starts upstream. First stabilize the inputs or stop recreating data structures unnecessarily. Then profile. Then add useMemo only where it reduces real work.
That matters even more as React tooling changes. The React Compiler can automate some optimization paths, but it does not remove the need to understand identity and data flow.
Practical Rule
Do not add useMemo because a value is "derived." Add it because profiling shows the recomputation or downstream re-render cost is worth trading for the extra complexity.
Further Reading