Last month, I was debugging a dashboard that felt sluggish. Every keystroke in the search box triggered a cascade of recalculations across thousands of rows. The culprit? A filter function running on every single render, processing the same unchanged data over and over.
"Why is my app calculating the same result 47 times when nothing changed?"
- Me, staring at React DevTools profiler
This is where useMemo saved the day. Instead of letting React mindlessly recompute expensive operations, I taught it to remember results and only recalculate when dependencies actually changed.
"Only recalculate when something meaningful changed, not just because React re-rendered."
What is useMemo and Why It Matters
useMemo is React's built-in memoization hook that caches expensive computation results between renders. It's essentially saying "React, remember this calculation and only redo it if the inputs actually change."
In my experience, it's most valuable for:
- Heavy data transformations (filtering, sorting, mapping large arrays)
- Complex calculations that depend on props or state
- Creating stable object references to prevent unnecessary child re-renders
Think of it as React's way of being smart about when to actually do work versus when to reuse previous results.
The Syntax That Actually Makes Sense
const memoizedValue = useMemo(() => {
// Your expensive calculation here
return computeExpensiveValue(dependencies);
}, [dependencies]);
The pattern I follow:
- ✅ First argument: A function that returns the computed value
- ✅ Second argument: Array of dependencies that trigger recalculation
- ✅ React compares dependencies using
Object.is()- same as useEffect
Real Example: The Dashboard That Taught Me useMemo
const ProductDashboard = ({ products, searchTerm, sortBy }) => {
// This was running on every render - even when just hovering buttons!
const processedProducts = useMemo(() => {
console.log('Processing products...'); // You'll see this less often now
return products
.filter(product =>
product.name.toLowerCase().includes(searchTerm.toLowerCase())
)
.sort((a, b) => {
if (sortBy === 'price') return a.price - b.price;
return a.name.localeCompare(b.name);
});
}, [products, searchTerm, sortBy]);
return (
<div>
<h2>Found {processedProducts.length} products</h2>
{processedProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
};
Before useMemo: Filter and sort ran on every render (even button hovers). After useMemo: Only runs when products, searchTerm, or sortBy actually change.
The performance difference was night and day - especially with 1000+ products.
useMemo vs useCallback: I Used to Confuse These
| Hook | What It Memoizes | When I Use It |
|---|---|---|
useMemo |
✅ The result of a computation | Expensive calculations, filtered data |
useCallback |
✅ The function itself | Event handlers, callback props |
Simple mental model: useMemo for values, useCallback for functions.
Production Patterns I Actually Use
- Search/filter results - Avoid refiltering unchanged data
- Expensive computations - Complex calculations that depend on props
- Stable object references - Prevent unnecessary re-renders in child components
- Data transformations - Mapping/sorting large arrays
- Derived state - Values computed from multiple state variables
Another Real Example: Search Results That Scale
const SearchResults = ({ users, query, filters }) => {
const filteredUsers = useMemo(() => {
if (!query && !filters.department) return users;
return users.filter(user => {
const matchesQuery = !query ||
user.name.toLowerCase().includes(query.toLowerCase()) ||
user.email.toLowerCase().includes(query.toLowerCase());
const matchesDepartment = !filters.department ||
user.department === filters.department;
return matchesQuery && matchesDepartment;
});
}, [users, query, filters.department]);
// This stays stable unless the filtered results actually change
const resultSummary = useMemo(() => ({
total: filteredUsers.length,
departments: [...new Set(filteredUsers.map(u => u.department))],
hasResults: filteredUsers.length > 0
}), [filteredUsers]);
return (
<div>
<SearchSummary summary={resultSummary} />
<UserList users={filteredUsers} />
</div>
);
};
✅ Both computations only run when their dependencies change, not on every component re-render.
❌ When I Don't Use useMemo (Hard-Learned Lessons)
Early in my React journey, I wrapped everything in useMemo. That was a mistake.
❌ I avoid it when:
- The computation is trivial - String concatenation, simple math
- Dependencies change on every render - You're just adding overhead
- The component rarely re-renders - No performance gain
- You're debugging - It can mask issues during development
Remember: useMemo has its own overhead. Use it when the benefits clearly outweigh the costs.
Battle-Tested Best Practices
| Practice | Why It Matters (From Experience) |
|---|---|
| Profile before optimizing | Use React DevTools Profiler to find actual bottlenecks |
| Include all dependencies | ESLint exhaustive-deps rule saves you from stale closures |
| Don't memoize primitives | React already optimizes primitive comparisons |
| Pair with React.memo | Child components need memo to benefit from stable props |
✅ useMemo Quick Reference
| Aspect | Details |
|---|---|
| Purpose | Cache expensive computations between renders |
| When it runs | Only when dependencies change (shallow comparison) |
| Returns | Memoized result of the computation function |
| Sweet spot | Heavy data processing, stable object references |
| Common mistake | Overusing it for simple calculations |
Questions I Get Asked About useMemo
1. How do I know when to use useMemo?
Profile your app first. If you see the same expensive computation running repeatedly when inputs haven't changed, that's your cue. I use React DevTools Profiler to spot these patterns.
2. What if I forget a dependency?
Your memoized value becomes stale - it won't update when it should. Install the eslint-plugin-react-hooks exhaustive-deps rule. It'll catch these bugs for you.
3. Can useMemo prevent child component re-renders?
Indirectly, yes. If you pass a memoized object/array to a React.memo component, it won't re-render when the reference stays the same. But the child needs to be wrapped in memo too.
4. Is there a performance cost to using useMemo?
Yes, there's overhead for the dependency comparison and caching. That's why I only use it when the computation cost clearly exceeds this overhead. Simple calculations don't need it.
5. Should I memoize everything for better performance?
Definitely not. I learned this the hard way. React is already fast for most operations. Measure first, optimize second. Unnecessary memoization can actually hurt performance.