Difficulty: Intermediate
useMemo is a React hook that memoizes the result of an expensive computation. It takes a function and a dependency array, and only recomputes the value when one of the dependencies changes. Between re-renders where the dependencies haven't changed, useMemo returns the previously cached result. This is a performance optimization tool, not a semantic guarantee.
The primary use case for useMemo is avoiding expensive recalculations on every render. If a component renders frequently (due to parent re-renders, context changes, or state updates) and contains a computation that processes large datasets, sorts arrays, or performs complex transformations, wrapping that computation in useMemo ensures it only runs when its inputs actually change.
The second important use case is maintaining referential equality. In JavaScript, objects and arrays are compared by reference, not by value. If you create a new object in the render body, it gets a new reference every render. This matters when that object is passed as a prop to a memoized child component (React.memo) or used as a dependency in useEffect or useCallback. useMemo ensures the same reference is returned when the underlying data hasn't changed.
However, useMemo itself has a cost - it uses memory to store the cached value and adds complexity to your code. React's documentation advises using it only when you can measurably demonstrate a performance benefit. Don't wrap every computation in useMemo preemptively. Profile first, then optimize. A good rule of thumb: if the computation takes less than a millisecond, the overhead of useMemo may outweigh its benefit.
React reserves the right to discard cached values under memory pressure or other internal heuristics. Your code should work correctly without useMemo - it should be a performance optimization, not a correctness requirement. If removing useMemo would break your logic, something else in your design needs to change.
function ExpensiveList({ items, filter }) {
const filteredItems = React.useMemo(() => {
console.log('Computing filtered items...');
return items.filter(item => item.includes(filter));
}, [items, filter]);
console.log('Filtered count:', filteredItems.length);
return null;
}
const items = ['apple', 'banana', 'avocado', 'blueberry', 'apricot'];
ExpensiveList({ items, filter: 'a' });
The filtering only runs when items or filter changes. On re-renders where neither dependency changes, useMemo returns the cached result without running the filter again.
function Dashboard({ userId }) {
const config = React.useMemo(() => {
console.log('Creating config object');
return {
userId: userId,
theme: 'dark',
pageSize: 20
};
}, [userId]);
console.log('Config userId:', config.userId);
console.log('Config theme:', config.theme);
// In real code: <MemoizedChild config={config} />
// Without useMemo, config would be a new object every render
// causing MemoizedChild to re-render unnecessarily
return null;
}
Dashboard({ userId: 5 });
useMemo ensures the config object keeps the same reference between renders when userId hasn't changed. This prevents unnecessary re-renders of memoized child components that receive config as a prop.
function SimpleComponent({ name }) {
// DON'T: Trivial computation, useMemo adds overhead
// const upper = React.useMemo(() => name.toUpperCase(), [name]);
// DO: Just compute directly
const upper = name.toUpperCase();
console.log('Name:', upper);
// DON'T: Primitive values don't need referential stability
// const doubled = React.useMemo(() => count * 2, [count]);
// DO: Compute directly for cheap operations
const length = name.length;
console.log('Length:', length);
console.log('Simple computations do not need useMemo');
return null;
}
SimpleComponent({ name: 'react' });
For trivial operations like string methods, arithmetic, or accessing properties, useMemo's overhead exceeds its benefit. Reserve it for genuinely expensive computations or when referential equality matters.
useMemo, memoization, expensive computations, referential equality, performance optimization