L4: useMemo Introduction
Optimize expensive calculations with React's useMemo hook
Learn how to prevent expensive calculations from running on every render with React's useMemo hook!
What You'll Learn
- What useMemo is and why it matters
- When to use useMemo
- When NOT to use useMemo
- Memoization concept
- Performance optimization patterns
- Prevent unnecessary recalculations
The Performance Problem
Every time a component re-renders, ALL its code runs again:
function HomePage() {
const { data: listings } = useFetch('/listings');
const [search, setSearch] = useState('');
// This runs on EVERY render!
const expensiveCalculation = () => {
console.log('Calculating...');
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += i;
}
return result;
};
const value = expensiveCalculation(); // Called every render!
return <div>{value}</div>;
}Problem: When you type in the search box:
searchstate changes- Component re-renders
expensiveCalculationruns again- Returns same result (wasteful!)
Unnecessary calculations slow down your app, especially with complex operations like filtering large arrays, sorting data, or computing derived values.
What is useMemo?
useMemo is a React hook that memoizes (caches) the result of a calculation. It only recalculates when dependencies change.
Memoization = Remember previous result, reuse it if inputs haven't changed
Think of it like a smart calculator:
- First time: Does the math, saves the answer
- Next times: Checks if numbers changed
- If same: Returns saved answer (fast! ⚡)
- If different: Does math again, saves new answer
useMemo Syntax
const memoizedValue = useMemo(() => {
// Expensive calculation here
return result;
}, [dependencies]);Parts:
- Function - Returns the value to memoize
- Dependencies - Array of values to watch
- Return value - The memoized result
How it works:
// First render
const result = useMemo(() => compute(a, b), [a, b]);
// Runs compute(a, b), saves result
// Second render (a and b unchanged)
const result = useMemo(() => compute(a, b), [a, b]);
// Returns saved result, doesn't run compute!
// Third render (a changed)
const result = useMemo(() => compute(a, b), [a, b]);
// Runs compute again because a changedSimple Example
function Component() {
const [count, setCount] = useState(0);
const [search, setSearch] = useState('');
// Runs on every render (even when search changes!)
const doubledCount = count * 2;
console.log('Calculated:', doubledCount);
return (
<div>
<p>Count: {count}</p>
<p>Doubled: {doubledCount}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
);
}Problem: Typing in search box triggers calculation, even though count didn't change!
function Component() {
const [count, setCount] = useState(0);
const [search, setSearch] = useState('');
// Only recalculates when count changes
const doubledCount = useMemo(() => {
console.log('Calculated:', count * 2);
return count * 2;
}, [count]); // Only depends on count
return (
<div>
<p>Count: {count}</p>
<p>Doubled: {doubledCount}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
);
}Solution: Typing in search box doesn't trigger calculation - count unchanged!
When to Use useMemo
Use useMemo for:
When NOT to Use useMemo
Don't use useMemo for:
Premature optimization - Only use useMemo when you have a performance problem. Adding it everywhere makes code harder to read and maintain.
Avoid for:
// ❌ Simple calculations
const double = useMemo(() => count * 2, [count]);
// Just do: const double = count * 2;
// ❌ Single operations
const uppercase = useMemo(() => name.toUpperCase(), [name]);
// Just do: const uppercase = name.toUpperCase();
// ❌ Small arrays
const filtered = useMemo(() => items.filter(i => i.active), [items]);
// Only optimize if items has 1000+ elements
// ❌ Primitive values
const formatted = useMemo(() => `$${price}`, [price]);
// String formatting is already fastRule of thumb: Only use useMemo when:
- Calculation takes > 5ms
- You're processing large datasets (100+ items)
- You've measured a performance problem
useMemo Best Practices
Always specify dependencies
// ✅ Good - all dependencies listed
const result = useMemo(() => {
return calculate(a, b, c);
}, [a, b, c]);
// ❌ Bad - missing dependencies
const result = useMemo(() => {
return calculate(a, b, c);
}, [a]); // b and c changes won't trigger recalculation!Don't mutate dependencies
// ❌ Bad - mutating array
const sorted = useMemo(() => {
return items.sort();
}, [items]); // Mutates original array!
// ✅ Good - create new array
const sorted = useMemo(() => {
return [...items].sort();
}, [items]); // Doesn't mutate originalKeep memoized functions pure
// ❌ Bad - has side effects
const result = useMemo(() => {
console.log('Calculating...'); // Side effect!
saveToLocalStorage(data); // Side effect!
return process(data);
}, [data]);
// ✅ Good - pure function
const result = useMemo(() => {
return process(data);
}, [data]);Use for expensive operations only
// ❌ Bad - simple operation
const double = useMemo(() => count * 2, [count]);
// ✅ Good - expensive operation
const filtered = useMemo(() => {
return largeArray
.filter(complexFilter)
.sort(complexSort)
.map(complexTransform);
}, [largeArray]);Measuring Performance
How do you know if useMemo helps?
Use React DevTools Profiler:
- Open React DevTools
- Click "Profiler" tab
- Click record button
- Interact with your app
- Stop recording
- Review render times
Look for:
- Components taking > 10ms
- Frequent re-renders
- Identical re-renders (wasted work)
Add console timing:
function Component() {
const result = useMemo(() => {
console.time('Expensive Calculation');
const result = expensiveOperation();
console.timeEnd('Expensive Calculation');
return result;
}, [deps]);
return <div>{result}</div>;
}Check browser console:
- First render: Shows time (e.g., "5.2ms")
- Later renders: No output (cached!)
Use Performance API:
function Component() {
const result = useMemo(() => {
const start = performance.now();
const result = expensiveOperation();
const end = performance.now();
console.log(`Took ${end - start}ms`);
return result;
}, [deps]);
return <div>{result}</div>;
}More accurate than console.time for micro-benchmarks.
Real Example: Filtering Data
function DataTable({ data, searchTerm }) {
// Without useMemo - runs on EVERY render
const filteredData = data.filter(item => {
return item.name.toLowerCase().includes(searchTerm.toLowerCase());
});
// With useMemo - only when data or searchTerm change
const filteredData = useMemo(() => {
return data.filter(item => {
return item.name.toLowerCase().includes(searchTerm.toLowerCase());
});
}, [data, searchTerm]);
return (
<table>
{filteredData.map(item => (
<tr key={item.id}>
<td>{item.name}</td>
</tr>
))}
</table>
);
}Performance comparison (with 10,000 items):
| Scenario | Without useMemo | With useMemo |
|---|---|---|
| First render | 15ms | 15ms |
| Re-render (same data) | 15ms | 0.1ms |
| Re-render (new data) | 15ms | 15ms |
Benefit: 99% faster for unchanged data! ⚡
Common Mistakes
What's Next?
In Lesson 5, we'll apply useMemo to optimize our HomePage filtering logic. We'll see real performance improvements when filtering large lists! 🚀
Summary
- ✅ useMemo memoizes expensive calculations
- ✅ Only recalculates when dependencies change
- ✅ Use for expensive operations on large data
- ✅ Don't use for simple calculations
- ✅ Always specify all dependencies
- ✅ Measure before optimizing
- ✅ Keep memoized functions pure
Key concept: useMemo = Smart caching for expensive calculations. Use it wisely!