TL;DR: useMemo tells React to remember the result of a calculation and only redo it when specific inputs change. It's a performance optimization — not a feature change. AI adds it when it detects expensive computations that don't need to run on every render. Most of the time AI is right to add it. Sometimes it's premature optimization. Here's how to tell the difference.
Why AI Coders Need This
React components re-render a lot. Every time state changes, every time a parent re-renders, your component runs again — including every calculation inside it. For a simple counter, this is invisible. For a component that filters 5,000 products, sorts them, and computes statistics, re-running those calculations on every keystroke in a search box is noticeably slow.
useMemo is React's way of saying: "Hey, you already calculated this, and the inputs haven't changed. Use the cached answer."
AI tools like Cursor and Claude Code add useMemo fairly aggressively. Sometimes it's genuinely necessary. Sometimes it's the equivalent of putting a turbocharger on a bicycle. This guide teaches you to tell the difference.
The Problem: React Recalculates Everything on Every Render
Here's a product list component. Watch what happens on every render:
function ProductList({ products, searchTerm, sortBy }) {
// This runs on EVERY render — even if products hasn't changed
const filtered = products.filter(p =>
p.name.toLowerCase().includes(searchTerm.toLowerCase())
);
// This ALSO runs on every render
const sorted = [...filtered].sort((a, b) => {
if (sortBy === 'price') return a.price - b.price;
if (sortBy === 'name') return a.name.localeCompare(b.name);
return 0;
});
// And THIS runs on every render too
const stats = {
total: sorted.length,
avgPrice: sorted.reduce((sum, p) => sum + p.price, 0) / sorted.length,
minPrice: Math.min(...sorted.map(p => p.price)),
maxPrice: Math.max(...sorted.map(p => p.price)),
};
return (
<div>
<p>{stats.total} products, avg ${stats.avgPrice.toFixed(2)}</p>
{sorted.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
With 50 products, you'll never notice. With 5,000? Every time the user types a letter, React filters 5,000 items, sorts the results, and computes statistics. The user presses a key and waits 200ms for the UI to respond. That's sluggish.
The fix isn't to change what the code does — it's to avoid doing the work when the inputs haven't changed.
The useMemo Solution
function ProductList({ products, searchTerm, sortBy }) {
// Only recalculates when products or searchTerm changes
const filtered = useMemo(() =>
products.filter(p =>
p.name.toLowerCase().includes(searchTerm.toLowerCase())
),
[products, searchTerm] // ← dependency array
);
// Only recalculates when filtered or sortBy changes
const sorted = useMemo(() =>
[...filtered].sort((a, b) => {
if (sortBy === 'price') return a.price - b.price;
if (sortBy === 'name') return a.name.localeCompare(b.name);
return 0;
}),
[filtered, sortBy]
);
// Only recalculates when sorted changes
const stats = useMemo(() => ({
total: sorted.length,
avgPrice: sorted.reduce((sum, p) => sum + p.price, 0) / sorted.length,
minPrice: Math.min(...sorted.map(p => p.price)),
maxPrice: Math.max(...sorted.map(p => p.price)),
}), [sorted]);
return (
<div>
<p>{stats.total} products, avg ${stats.avgPrice.toFixed(2)}</p>
{sorted.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
Now when the user clicks a button that changes some unrelated state (like opening a sidebar), React re-renders this component but skips all three calculations because products, searchTerm, and sortBy haven't changed. It returns the cached results instantly.
How useMemo Works (The Mental Model)
Think of useMemo as a sticky note on your desk:
- First render: React runs your calculation, writes the result on a sticky note, and saves it
- Next render: React checks the dependency array. Did any of those values change?
- If nothing changed: React reads the sticky note instead of recalculating. Instant.
- If something changed: React throws away the old sticky note, runs the calculation again, and writes a new one.
The syntax is always the same:
const cachedValue = useMemo(
() => expensiveCalculation(input1, input2), // the calculation
[input1, input2] // when to recalculate
);
The dependency array is the critical part. It tells React: "Only redo this calculation when these specific values change." Get it wrong and you either recalculate too often (wasting the optimization) or not enough (stale data bug).
useMemo vs useCallback: The Confusion AI Creates
AI constantly mixes these up. Here's the simple difference:
| Hook | What It Caches | Returns | Use When |
|---|---|---|---|
useMemo | A computed value | The result of a function | Expensive calculations, derived data |
useCallback | A function definition | The function itself | Passing callbacks to child components |
// useMemo — caches the RESULT
const sortedList = useMemo(() => items.sort(), [items]);
// useCallback — caches the FUNCTION
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
If AI writes useMemo(() => () => doSomething(), [dep]) — a useMemo that returns a function — it should be useCallback(() => doSomething(), [dep]) instead. Tell it: "This should be useCallback, not useMemo returning a function."
When useMemo Is Worth It (and When It's Not)
- Filtering or sorting arrays with 100+ items
- Computing derived data from large datasets (totals, averages, grouping)
- Creating objects or arrays passed to child components wrapped in
React.memo - Expensive string operations (regex on large text, markdown parsing)
- Complex mathematical computations
- Simple variable assignments:
const fullName = first + ' ' + last; - Basic math:
const total = price * quantity; - Boolean checks:
const isAdmin = user.role === 'admin'; - Small array operations (under 50 items)
- Values that change on every render anyway (makes caching pointless)
The rule of thumb: If you can't feel the performance difference when you remove useMemo, you don't need it. The React team's official guidance is: "Write the code without useMemo first. Add it only when you have a measurable performance problem."
What AI Gets Wrong About useMemo
AI treats useMemo like bubble wrap — wraps everything just in case. You'll see useMemo(() => count + 1, [count]) — memoizing addition. The cost of tracking the dependency array exceeds the cost of adding two numbers. Fix: Ask AI "Is this useMemo necessary? The calculation is trivial." AI will usually admit it and remove it.
AI sometimes lists too few dependencies (stale data) or too many (useMemo never caches because something always changes). A common mistake: including setState functions in the dependency array — these never change and shouldn't be listed. Fix: Install eslint-plugin-react-hooks — the exhaustive-deps rule catches these automatically.
AI occasionally puts API calls, localStorage writes, or console.log statements inside useMemo. This is wrong — useMemo is for pure calculations, not side effects. Side effects go in useEffect. If your memoized function changes anything outside itself, move it to useEffect.
useMemo caches a value inside a component. React.memo caches an entire component to prevent re-rendering when props haven't changed. They're different tools. AI sometimes wraps a component in useMemo when it should use React.memo, or vice versa. Fix: "Did you mean React.memo for the component, or useMemo for the value?"
React 19 and the Future of useMemo
Here's the plot twist: React 19 includes a compiler that can automatically add memoization. The React team's long-term goal is to make useMemo and useCallback unnecessary — the compiler will figure out what needs caching.
Does that mean you shouldn't learn useMemo? No. The compiler is opt-in, not everything is compiled automatically, and understanding why memoization exists helps you debug performance issues regardless of whether you write it manually or the compiler adds it. Plus, AI will generate useMemo code for years — it's everywhere in training data.
Debugging useMemo with AI
"My component re-renders every time I type in the search box and it's slow. Here's the component: [paste]. Which calculations should I wrap in useMemo, and which are too simple to bother?"
AI is actually good at this — better at analyzing existing code for memoization opportunities than deciding during initial generation. Give it your slow component and ask specifically "what's expensive here?" rather than letting it guess.
Install React DevTools, open the Profiler tab, and record a few interactions. It shows exactly which components re-rendered, how long each render took, and why it re-rendered. This is 100x more useful than guessing where to add useMemo.
Frequently Asked Questions
useMemo caches a computed value (like a filtered list). useCallback caches a function itself. They use the same mechanism — dependency arrays — but useMemo returns data and useCallback returns a function. If AI wraps a function definition in useMemo, it should probably be useCallback instead.
Only if the computation it wraps is expensive. For filtering 10 items, useMemo adds overhead without benefit. For sorting 10,000 items, it prevents React from recalculating on every render. Profile with React DevTools before optimizing — you might be surprised where the actual bottleneck is.
Don't use it for simple values, string concatenation, basic math, or any operation that takes less than a millisecond. Don't use it just because AI suggests it. If removing useMemo doesn't cause a visible performance difference, you don't need it.
Memoization means remembering the answer so you don't have to calculate it again. If someone asks "what's 847 × 293?" you'd calculate it once (248,171) and remember. Next time they ask, you recall the answer instead of doing the math. useMemo does this for your React components.
Yes, if the dependency array is wrong. Missing a dependency means useMemo returns a stale value because it doesn't know something changed. The exhaustive-deps ESLint rule catches this — always enable it.