useMemo is a React Hook that lets you cache the result of a calculation between re-renders.
function useMemo<T>(
create: () => T,
deps: Array<mixed> | void | null
): T
Parameters
The function calculating the value that you want to cache. It should be pure, take no arguments, and return a value of any type. React will call your function during the initial render. On subsequent renders, React will return the same value if the dependencies haven’t changed. Otherwise, it will call create, return its result, and store it for later reuse.
deps
Array<mixed> | void | null
The list of all reactive values referenced inside the create code. React will compare each dependency with its previous value using the Object.is comparison.
- If omitted, useMemo will recalculate on every render (defeats the purpose)
- If
[] (empty array), the value is calculated only once
- If
[dep1, dep2], recalculates when dependencies change
Returns
On the initial render, useMemo returns the result of calling create with no arguments.
During subsequent renders, it will either return an already stored value from the last render (if the dependencies haven’t changed), or call create again and return the result that create has returned.
Usage
Skipping expensive recalculations
To cache a calculation between re-renders, wrap it in useMemo:
import { useMemo } from 'react';
function TodoList({ todos, filter }) {
const visibleTodos = useMemo(() => {
// Only recalculates when todos or filter changes
return todos.filter(todo => {
if (filter === 'active') {
return !todo.completed;
}
if (filter === 'completed') {
return todo.completed;
}
return true;
});
}, [todos, filter]);
return (
<ul>
{visibleTodos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
Skipping re-rendering of components
When you pass an object or array to a child component, create it with useMemo to keep the same reference:
import { useMemo, memo } from 'react';
function Parent({ items }) {
// ❌ New object every render - child always re-renders
const options = { sort: 'asc', filter: 'all' };
// ✅ Same object reference - child only re-renders when deps change
const options = useMemo(() => {
return { sort: 'asc', filter: 'all' };
}, []);
return <MemoizedChild items={items} options={options} />;
}
const MemoizedChild = memo(function Child({ items, options }) {
// ...
});
This pattern is only useful when the child component is wrapped in memo. Without memo, the child will re-render anyway.
Memoizing a dependency of another Hook
Prevent an effect from running unnecessarily:
function Dropdown({ options }) {
// ❌ searchOptions is a new object every render
const searchOptions = {
options: options,
caseSensitive: false
};
useEffect(() => {
// Effect runs on every render!
}, [searchOptions]);
// ✅ searchOptions only changes when options changes
const searchOptions = useMemo(() => {
return {
options: options,
caseSensitive: false
};
}, [options]);
useEffect(() => {
// Effect only runs when searchOptions actually changes
}, [searchOptions]);
}
Memoizing a function
Use useCallback instead:
// These are equivalent:
const memoizedFunction = useMemo(() => {
return (a, b) => a + b;
}, []);
const memoizedFunction = useCallback((a, b) => {
return a + b;
}, []);
Common Patterns
Expensive calculations
Sorting
Filtering
Aggregation
function Table({ data, sortKey }) {
const sortedData = useMemo(() => {
return [...data].sort((a, b) => {
return a[sortKey] > b[sortKey] ? 1 : -1;
});
}, [data, sortKey]);
return <TableView data={sortedData} />;
}
function SearchResults({ items, query }) {
const results = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
}, [items, query]);
return <ResultsList results={results} />;
}
function Dashboard({ transactions }) {
const stats = useMemo(() => {
return {
total: transactions.reduce((sum, t) => sum + t.amount, 0),
count: transactions.length,
average: transactions.reduce((sum, t) => sum + t.amount, 0) / transactions.length
};
}, [transactions]);
return <StatsView stats={stats} />;
}
Creating objects and arrays
function Chart({ data, width, height }) {
// Memoize config object
const chartConfig = useMemo(() => ({
width,
height,
margins: { top: 20, right: 20, bottom: 30, left: 40 },
colors: ['#ff0000', '#00ff00', '#0000ff']
}), [width, height]);
return <ChartComponent data={data} config={chartConfig} />;
}
Computing derived state
function UserProfile({ user }) {
const displayName = useMemo(() => {
if (user.nickname) {
return user.nickname;
}
if (user.firstName && user.lastName) {
return `${user.firstName} ${user.lastName}`;
}
return user.email;
}, [user.nickname, user.firstName, user.lastName, user.email]);
return <h1>{displayName}</h1>;
}
Preventing unnecessary deep comparisons
function TodoList({ todos }) {
// Extract IDs once
const todoIds = useMemo(() => {
return todos.map(todo => todo.id);
}, [todos]);
useEffect(() => {
// Only runs when the list of IDs changes
updateVisibleTodos(todoIds);
}, [todoIds]);
}
TypeScript
import { useMemo } from 'react';
// Type is inferred from return value
const sortedItems = useMemo(() => {
return items.sort((a, b) => a.value - b.value);
}, [items]);
// Type: typeof items
// Explicitly specify type
interface Stats {
total: number;
average: number;
}
const stats = useMemo<Stats>(() => {
return {
total: items.reduce((sum, item) => sum + item.value, 0),
average: items.reduce((sum, item) => sum + item.value, 0) / items.length
};
}, [items]);
// With complex types
interface User {
id: string;
name: string;
}
const userMap = useMemo<Map<string, User>>(() => {
const map = new Map();
users.forEach(user => map.set(user.id, user));
return map;
}, [users]);
Troubleshooting
My calculation runs on every render
Make sure you provided a dependency array:
// ❌ Missing dependency array
const value = useMemo(() => {
return expensiveCalculation();
}); // Recalculates every render!
// ✅ With dependency array
const value = useMemo(() => {
return expensiveCalculation();
}, []); // Calculates once
My useMemo call runs on every render despite dependencies
One of your dependencies might be changing on every render:
function Component() {
const items = [1, 2, 3]; // New array every render
const doubled = useMemo(() => {
return items.map(x => x * 2);
}, [items]); // items changes every render!
}
Solutions:
- Move constant values outside the component
- Use
useMemo for the dependency too
- Depend on individual values instead of objects/arrays
How to tell if a calculation is expensive?
Add a console.time to measure:
function Component() {
const result = useMemo(() => {
console.time('calculation');
const value = expensiveCalculation();
console.timeEnd('calculation');
return value;
}, [deps]);
}
If the calculation takes less than 1ms, it’s probably not worth memoizing. The overhead of useMemo itself might be more expensive than the calculation.
Should I add useMemo everywhere?
No! Only use useMemo when:
- The calculation is expensive (takes >1ms)
- You’re creating objects/arrays passed to memoized components
- The value is used as a dependency in another Hook
- You’ve measured and confirmed it improves performance
// ❌ Unnecessary - simple calculation
const doubled = useMemo(() => count * 2, [count]);
// ✅ Better - just calculate it
const doubled = count * 2;
// ❌ Unnecessary - not passed to memoized component
const items = useMemo(() => {
return data.map(x => ({ ...x, formatted: format(x) }));
}, [data]);
return <RegularComponent items={items} />;
// ✅ Necessary - passed to memoized component
const items = useMemo(() => {
return data.map(x => ({ ...x, formatted: format(x) }));
}, [data]);
return <MemoizedComponent items={items} />;
Don’t optimize prematurely
- Write code normally first
- Use React DevTools Profiler to find slow components
- Add
useMemo only where it helps
- Measure again to confirm improvement
Common mistakes
// ❌ Memoizing a primitive - no benefit
const value = useMemo(() => props.count * 2, [props.count]);
// ✅ Just calculate it
const value = props.count * 2;
// ❌ Expensive dependency calculation defeats the purpose
const filtered = useMemo(() => {
return items.filter(matchesFilter);
}, [items, items.map(i => i.id)]); // map runs every time!
// ✅ Only depend on what changes
const filtered = useMemo(() => {
return items.filter(matchesFilter);
}, [items]);
When NOT to use useMemo
- Primitive calculations (math, string operations)
- Creating elements (
<div>, <Component />)
- Simple array operations on small arrays (less than 100 items)
- Object/array literals that aren’t passed anywhere
useMemo vs useCallback
// useMemo: Cache a computed value
const sortedItems = useMemo(() => {
return items.sort((a, b) => a.value - b.value);
}, [items]);
// useCallback: Cache a function
const handleSort = useCallback(() => {
setSortedItems(items.sort((a, b) => a.value - b.value));
}, [items]);
| Hook | Caches | Use Case |
|---|
useMemo | The result | Expensive calculations, derived state |
useCallback | The function | Passing callbacks to memoized components |