useMemo
useMemo is a React Hook that caches the result of a calculation between re-renders. When a component re-renders, every expression in its body normally runs again. If a calculation is expensive, or if it produces an object or array whose identity matters downstream, recomputing it on every render can hurt performance or break memoized children. useMemo lets you skip that work unless its inputs actually change.
How useMemo works
You call useMemo with a calculation function and a dependency array. React runs the function on the first render and stores the result. On later renders it compares each dependency to its previous value using Object.is. If nothing changed, React returns the cached value without re-running the function; if any dependency changed, it re-runs the function and caches the new result.
import { useMemo, useState } from 'react';
function ProductList({ products, filter }) {
const visibleProducts = useMemo(() => {
console.log('Filtering products...');
return products.filter((p) => p.name.includes(filter));
}, [products, filter]);
return (
<ul>
{visibleProducts.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
Here the filtering runs only when products or filter changes. Typing in an unrelated input that triggers a re-render no longer re-filters the list.
The function you pass to
useMemomust be pure and take no arguments. It should compute and return a value based only on the dependencies you list. Do not perform side effects such as fetching or subscriptions here — that belongs inuseEffect.
Memoizing an expensive calculation
The classic use case is a calculation that takes a noticeable amount of time. A good rule of thumb: if it takes more than a millisecond, it may be worth memoizing.
import { useMemo, useState } from 'react';
function Statistics({ numbers }) {
const [theme, setTheme] = useState('light');
const stats = useMemo(() => {
const sorted = [...numbers].sort((a, b) => a - b);
const total = sorted.reduce((sum, n) => sum + n, 0);
const mid = Math.floor(sorted.length / 2);
const median =
sorted.length % 2 === 0
? (sorted[mid - 1] + sorted[mid]) / 2
: sorted[mid];
return { mean: total / sorted.length, median, max: sorted.at(-1) };
}, [numbers]);
return (
<section className={theme}>
<p>Mean: {stats.mean}</p>
<p>Median: {stats.median}</p>
<button onClick={() => setTheme((t) => (t === 'light' ? 'dark' : 'light'))}>
Toggle theme
</button>
</section>
);
}
Toggling the theme re-renders the component but does not recompute stats, because numbers is unchanged.
Keeping references stable for memoized children
The second major use case has nothing to do with slow math. JavaScript creates a brand-new object or array literal on every render, so { id: 1 } is never Object.is-equal to the previous { id: 1 }. If you pass such a value as a prop to a component wrapped in React.memo, the child re-renders every time even though the data is identical.
import { memo, useMemo, useState } from 'react';
const Chart = memo(function Chart({ options }) {
console.log('Chart re-rendered');
return <div>Rendering chart with theme {options.theme}</div>;
});
function Dashboard({ theme }) {
const [count, setCount] = useState(0);
// Stable across re-renders unless `theme` changes
const chartOptions = useMemo(() => ({ theme, animate: true }), [theme]);
return (
<>
<button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
<Chart options={chartOptions} />
</>
);
}
Output:
Chart re-rendered // first render only
// Clicking the button no longer logs — Chart's props are referentially stable
Without useMemo, every click on the counter would create a fresh chartOptions object and force Chart to re-render despite React.memo.
When NOT to use useMemo
useMemo is an optimization, not a default. It is not free: React stores the cached value and the dependency array, and it runs the comparison on every render. Over-applying it adds noise and can even slow code down.
| Situation | Use useMemo? |
|---|---|
| Cheap calculation (simple arithmetic, short strings) | No — just compute inline |
| Genuinely expensive calculation, measured | Yes |
Object/array passed to a memo-wrapped child | Yes |
| Value used in another Hook’s dependency array | Yes, to keep that array stable |
| Memoizing a function instead of a value | No — use useCallback |
Measure before you optimize. Use the React DevTools Profiler or wrap the calculation in
console.time/console.timeEndto confirm a real cost. AddinguseMemoeverywhere “just in case” makes code harder to read without a measurable win.
A practical way to test whether something is expensive enough to memoize is to temporarily slow it down and profile in a production build, since development builds are not representative of real performance.
Best Practices
- Memoize only after measuring a real cost — expensive calculations or reference-stability problems, not every value.
- List every reactive value the calculation reads in the dependency array; missing dependencies cause stale results.
- Keep the calculation pure and side-effect free; move fetching and subscriptions to
useEffect. - Reach for
useMemoto stabilize object/array props passed toReact.memochildren, and to keep other Hooks’ dependency arrays stable. - Use
useCallbackfor functions anduseMemofor the values those functions return. - Do not rely on
useMemofor correctness — React may discard the cache (for example, when unmounting); your component must still work if the value is recomputed.