Optimizing Performance with useMemo in React
A common performance oversight in React applications is failing to memoize expensive computations, which can lead to unnecessary re-renders and sluggish UI. Here’s how to properly use useMemo
to optimize your components.
The Core Problem
Problematic implementation without memoization:
function ProductList({ products, filters }) {
// ❌ Expensive computation runs on every render
const filteredProducts = products.filter(p =>
p.price >= filters.minPrice &&
p.price <= filters.maxPrice
);
return (
<ul>
{filteredProducts.map(product => (
<ProductItem key={product.id} product={product} />
))}
</ul>
);
}
Issues this causes:
- Unnecessary recomputation on every render
- Performance degradation with large datasets
- Janky UI during interactions
- Reduced battery life on mobile devices
Correct Implementation with useMemo
1. Basic Memoization
function ProductList({ products, filters }) {
// ✅ Only recomputes when products or filters change
const filteredProducts = useMemo(() => {
return products.filter(p =>
p.price >= filters.minPrice &&
p.price <= filters.maxPrice
);
}, [products, filters]);
return (
<ul>
{filteredProducts.map(product => (
<ProductItem key={product.id} product={product} />
))}
</ul>
);
}
2. Complex Computations
function Dashboard({ transactions }) {
// ✅ Memoizes multiple expensive calculations
const { total, average, chartData } = useMemo(() => {
const total = transactions.reduce((sum, t) => sum + t.amount, 0);
const average = total / transactions.length;
const chartData = processTransactionsForChart(transactions);
return { total, average, chartData };
}, [transactions]);
return (
<div>
<Summary total={total} average={average} />
<Chart data={chartData} />
</div>
);
}
When to Use useMemo
Appropriate Use Cases:
- Filtering/sorting large arrays
- Complex calculations
- Formatting/transforming data
- Deriving state from props
- Component props that don’t need to change often
When Not to Use:
- Simple calculations (addition, string concatenation)
- Primitive values that don’t involve computation
- Cases where recomputation is cheaper than memoization overhead
Common Mistakes and Solutions
❌ Missing Dependencies
const result = useMemo(() => {
return compute(a, b); // Uses a and b
}, [a]); // ❌ Missing b dependency
✅ Fix: Include all dependencies
const result = useMemo(() => {
return compute(a, b);
}, [a, b]); // ✅ Correct dependencies
❌ Overusing useMemo
const fullName = useMemo(() => {
return `${firstName} ${lastName}`; // ❌ Too simple to memoize
}, [firstName, lastName]);
✅ Fix: Only memoize expensive operations
const fullName = `${firstName} ${lastName}`; // ✅ Simple concatenation
�Using useMemo for Components
const MemoizedComponent = useMemo(() => {
return <ExpensiveComponent data={data} />; // ❌ Wrong pattern
}, [data]);
✅ Fix: Use React.memo instead
const MemoizedComponent = React.memo(ExpensiveComponent);
Advanced Patterns
1. Combining with useCallback
function Parent({ items }) {
// Memoize both data and handlers
const processedItems = useMemo(() => processItems(items), [items]);
const handleSelect = useCallback((id) => {
// Handle selection
}, []);
return <Child items={processedItems} onSelect={handleSelect} />;
}
2. Custom Comparison Function
function useDeepMemo(factory, deps) {
const prevDeps = useRef(deps);
if (!deepEqual(prevDeps.current, deps)) {
prevDeps.current = deps;
}
return useMemo(factory, [prevDeps.current]);
}
// Usage
const result = useDeepMemo(() => compute(a, b), [a, b]);
3. Memoizing Context Value
function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
// Memoize context value to prevent unnecessary re-renders
const contextValue = useMemo(() => ({
state,
dispatch
}), [state]);
return (
<AppContext.Provider value={contextValue}>
{children}
</AppContext.Provider>
);
}
Performance Measurement
Verify optimizations with:
- React DevTools Profiler
- console.time()/console.timeEnd()
- Performance tab in browser devtools
Example measurement:
function Component() {
console.time('compute');
const data = useMemo(() => expensiveCompute(), [deps]);
console.timeEnd('compute'); // Logs computation time
return /* ... */;
}
Key Takeaways
- Memoize expensive computations with
useMemo
- Include all dependencies in the dependency array
- Don’t overuse – apply only where beneficial
- Combine with React.memo for component optimization
- Measure before/after to validate improvements
Proper use of useMemo
can significantly improve performance in React applications by avoiding unnecessary recomputations during renders. However, it should be applied judiciously where measurable benefits exist.