Advanced React Hooks: useCallback and useMemo
Master React's performance optimization hooks. Learn when and how to use useCallback and useMemo effectively in your applications.
Understanding React Performance Optimization
React’s useCallback and useMemo hooks are powerful tools for optimizing component performance. However, they’re often misunderstood and misused. Let’s dive deep into when and how to use them effectively.
useCallback: Memoizing Functions
The useCallback hook returns a memoized version of the callback that only changes if one of the dependencies has changed.
Basic Usage
import { useCallback, useState } from 'react';
function SearchComponent() {
const [query, setQuery] = useState('');
const handleSearch = useCallback((value) => {
// This function is only recreated when query changes
console.log('Searching for:', value);
setQuery(value);
}, [query]);
return <SearchInput onSearch={handleSearch} />;
}
When to Use useCallback
Use useCallback when:
- Passing callbacks to optimized child components
- The callback is a dependency of other hooks
- You want to prevent unnecessary re-renders
useMemo: Memoizing Values
The useMemo hook returns a memoized value that only recomputes when dependencies change.
Example
import { useMemo, useState } from 'react';
function ExpensiveComponent({ items }) {
const [filter, setFilter] = useState('');
const filteredItems = useMemo(() => {
console.log('Filtering items...');
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [items, filter]);
return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
Common Pitfalls
Over-optimization
Don’t optimize prematurely:
// ❌ Bad: Unnecessary memoization
const value = useMemo(() => 2 + 2, []);
// ✅ Good: Simple calculations don't need memoization
const value = 2 + 2;
Missing Dependencies
// ❌ Bad: Missing dependency
const calculate = useCallback(() => {
return value * 2;
}, []); // value is missing!
// ✅ Good: All dependencies included
const calculate = useCallback(() => {
return value * 2;
}, [value]);
Best Practices
- Profile First: Use React DevTools Profiler to identify actual performance issues
- Measure Impact: Verify that memoization actually improves performance
- Keep Dependencies Minimal: Fewer dependencies mean better memoization
- Don’t Memoize Everything: These hooks add overhead
Real-World Example
function DataTable({ data, sortBy }) {
const sortedData = useMemo(() => {
console.log('Sorting data...');
return [...data].sort((a, b) => {
return a[sortBy] > b[sortBy] ? 1 : -1;
});
}, [data, sortBy]);
const handleRowClick = useCallback((rowId) => {
console.log('Row clicked:', rowId);
// Handle row click
}, []);
return (
<table>
<tbody>
{sortedData.map(row => (
<TableRow
key={row.id}
data={row}
onClick={handleRowClick}
/>
))}
</tbody>
</table>
);
}
Conclusion
UseCallback and useMemo are powerful optimization tools, but use them wisely. Profile your application, identify bottlenecks, and apply memoization where it makes a measurable difference. Remember: premature optimization is the root of all evil!