useMemo and useCallback - Performance Optimization
useMemo - Memoize Expensive Calculations
function ExpensiveComponent({ items, filter }) {
// ❌ Recalculates on every render
const filteredItems = items.filter(item => item.category === filter);
// ✅ Only recalculates when dependencies change
const filteredItems = useMemo(
() => items.filter(item => item.category === filter),
[items, filter]
);
return <List items={filteredItems} />;
}
Real-World Examples
// Expensive sorting/filtering
const sortedUsers = useMemo(() => {
return [...users].sort((a, b) => a.name.localeCompare(b.name));
}, [users]);
// Computed values
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}, [items]);
// Derived state
const { activeUsers, inactiveUsers } = useMemo(() => {
return {
activeUsers: users.filter(u => u.isActive),
inactiveUsers: users.filter(u => !u.isActive),
};
}, [users]);
useCallback - Memoize Function References
function Parent() {
const [count, setCount] = useState(0);
// ❌ New function on every render
const handleClick = () => {
console.log('clicked');
};
// ✅ Stable function reference
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return <Child onClick={handleClick} />;
}
const Child = React.memo(({ onClick }) => {
return <button onClick={onClick}>Click</button>;
});
With Dependencies
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const search = useCallback(async () => {
const data = await searchAPI(query);
setResults(data);
}, [query]); // Recreates when query changes
useEffect(() => {
search();
}, [search]);
return <div>{/* results */}</div>;
}
When to Use
Use useMemo when:
- Expensive calculations
- Preventing child re-renders
- Referential equality matters
Use useCallback when:
- Passing callbacks to optimized child components
- Dependency in useEffect
- Custom hooks that accept callbacks
Practice Exercise
Optimize a product list with filtering, sorting, and search.
Next Steps
- Learn about Custom Hooks