useCallback & useMemo
Why Is Memoization Important?
React re-renders child components every time the parent re-renders. Memoization prevents unnecessary re-renders by caching function results or values.
useCallback to Memoize Functions
useCallback returns a memoized function. It is useful when a function is passed as a prop.
import React, { useState, useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
const handleClick = useCallback(() => {
console.log('Clicked');
}, []); // The function is not recreated
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<input value={text} onChange={e => setText(e.target.value)} />
<MemoizedChild onClick={handleClick} />
</div>
);
}
const MemoizedChild = React.memo(({ onClick }) => {
console.log('Child render');
return <button onClick={onClick}>Child Button</button>;
});
useMemo to Memoize Values
useMemo returns a memoized value. It is useful for expensive computations.
function TodoList({ todos, filter }) {
const visibleTodos = useMemo(() => {
return todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
}, [todos, filter]); // Only recomputes when dependencies change
return <List items={visibleTodos} />;
}
Dependency Array
// The function is only recreated when a or b changes
const memoizedFn = useCallback(() => {
return a + b;
}, [a, b]);
// The value is only recomputed when items changes
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.price, 0);
}, [items]);
When to Use Them?
| Use | When |
|---|---|
useCallback |
Function is passed to a memoized child |
useMemo |
Expensive computation or a new object/array |
| Not needed | Simple components, rendering is not expensive |
Example: Performance Optimization
function ExpensiveList({ items, onSelect }) {
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
const handleSelect = useCallback((id) => {
onSelect(id);
}, [onSelect]);
return (
<ul>
{sortedItems.map(item => (
<ExpensiveItem
key={item.id}
item={item}
onSelect={handleSelect}
/>
))}
</ul>
);
}
const ExpensiveItem = React.memo(({ item, onSelect }) => {
// Only re-renders when item or onSelect changes
return (
<li onClick={() => onSelect(item.id)}>
{item.name} - {item.price}
</li>
);
});
Common Mistakes
// Wrong: incomplete dependency array
const memoizedFn = useCallback(() => {
doSomething(a, b);
}, [a]); // Forgot b
// Correct
const memoizedFn = useCallback(() => {
doSomething(a, b);
}, [a, b]);
useCallback and useMemo help optimize performance. Next we will study useReducer for more complex state management.