useMemo and useCallback
These hooks help optimize performance by memoizing values and functions to prevent unnecessary recalculations and re-renders.
useMemo
Memoizes a computed value:
jsx
1import { useMemo } from 'react'
2
3function ExpensiveComponent({ items, filter }) {
4 // Without useMemo - runs on every render
5 const filteredItems = items.filter(item => item.includes(filter))
6
7 // With useMemo - only recalculates when items or filter change
8 const filteredItems = useMemo(() => {
9 return items.filter(item => item.includes(filter))
10 }, [items, filter])
11
12 return <List items={filteredItems} />
13}When to Use useMemo
jsx
1// ✅ Good: Expensive calculations
2const sortedData = useMemo(() => {
3 return [...data].sort((a, b) => a.name.localeCompare(b.name))
4}, [data])
5
6// ✅ Good: Creating objects to pass as props
7const style = useMemo(() => ({
8 color: theme === 'dark' ? 'white' : 'black',
9 fontSize: size * 2
10}), [theme, size])
11
12// ✅ Good: Complex filtering/transformation
13const visibleTodos = useMemo(() => {
14 return todos
15 .filter(todo => !todo.completed || showCompleted)
16 .sort((a, b) => b.priority - a.priority)
17 .slice(0, limit)
18}, [todos, showCompleted, limit])
19
20// ❌ Bad: Simple values (overhead not worth it)
21const doubled = useMemo(() => count * 2, [count])useMemo Examples
jsx
1function SearchResults({ query, allItems }) {
2 // Expensive search operation
3 const results = useMemo(() => {
4 console.log('Searching...')
5 return allItems.filter(item => {
6 // Complex matching logic
7 return item.title.toLowerCase().includes(query.toLowerCase()) ||
8 item.tags.some(tag => tag.includes(query))
9 })
10 }, [query, allItems])
11
12 // Even more expensive: sorting results
13 const sortedResults = useMemo(() => {
14 return [...results].sort((a, b) => b.relevance - a.relevance)
15 }, [results])
16
17 return (
18 <ul>
19 {sortedResults.map(item => (
20 <li key={item.id}>{item.title}</li>
21 ))}
22 </ul>
23 )
24}useCallback
Memoizes a function:
jsx
1import { useCallback } from 'react'
2
3function ParentComponent() {
4 const [count, setCount] = useState(0)
5
6 // Without useCallback - new function every render
7 const handleClick = () => {
8 console.log('Clicked')
9 }
10
11 // With useCallback - same function reference
12 const handleClick = useCallback(() => {
13 console.log('Clicked')
14 }, [])
15
16 return <ChildComponent onClick={handleClick} />
17}Why useCallback Matters
jsx
1// Child component wrapped in React.memo
2const ExpensiveChild = React.memo(function ExpensiveChild({ onClick }) {
3 console.log('Child rendered')
4 return <button onClick={onClick}>Click me</button>
5})
6
7function Parent() {
8 const [count, setCount] = useState(0)
9
10 // ❌ Without useCallback: Child re-renders every time Parent renders
11 const handleClick = () => console.log('click')
12
13 // ✅ With useCallback: Child only re-renders when dependencies change
14 const handleClick = useCallback(() => console.log('click'), [])
15
16 return (
17 <div>
18 <p>Count: {count}</p>
19 <button onClick={() => setCount(c => c + 1)}>Increment</button>
20 <ExpensiveChild onClick={handleClick} />
21 </div>
22 )
23}useCallback with Dependencies
jsx
1function TodoList({ todos, onToggle }) {
2 // Depends on onToggle prop
3 const handleToggle = useCallback((id) => {
4 onToggle(id)
5 }, [onToggle])
6
7 return (
8 <ul>
9 {todos.map(todo => (
10 <TodoItem
11 key={todo.id}
12 todo={todo}
13 onToggle={handleToggle}
14 />
15 ))}
16 </ul>
17 )
18}
19
20// Using state in callback
21function Counter() {
22 const [count, setCount] = useState(0)
23
24 // ❌ This captures stale count
25 const logCount = useCallback(() => {
26 console.log(count) // Always logs initial count
27 }, [])
28
29 // ✅ Include count in dependencies
30 const logCount = useCallback(() => {
31 console.log(count)
32 }, [count])
33
34 // ✅ Or use functional update to avoid dependency
35 const increment = useCallback(() => {
36 setCount(c => c + 1) // No dependency on count
37 }, [])
38}useCallback vs useMemo
jsx
1// useCallback returns a function
2const memoizedFn = useCallback(() => {
3 doSomething(a, b)
4}, [a, b])
5
6// useMemo returns a value (can be a function)
7const memoizedFn = useMemo(() => {
8 return () => doSomething(a, b)
9}, [a, b])
10
11// These are equivalent!
12useCallback(fn, deps) === useMemo(() => fn, deps)Combined Usage
jsx
1function ProductList({ products, sortBy, filterBy }) {
2 // Memoize filtered and sorted data
3 const displayProducts = useMemo(() => {
4 let result = products.filter(p => p.category === filterBy)
5
6 if (sortBy === 'price') {
7 result = [...result].sort((a, b) => a.price - b.price)
8 } else if (sortBy === 'name') {
9 result = [...result].sort((a, b) => a.name.localeCompare(b.name))
10 }
11
12 return result
13 }, [products, sortBy, filterBy])
14
15 // Memoize event handlers
16 const handleAddToCart = useCallback((productId) => {
17 addToCart(productId)
18 }, [])
19
20 const handleFavorite = useCallback((productId) => {
21 toggleFavorite(productId)
22 }, [])
23
24 return (
25 <div>
26 {displayProducts.map(product => (
27 <ProductCard
28 key={product.id}
29 product={product}
30 onAddToCart={handleAddToCart}
31 onFavorite={handleFavorite}
32 />
33 ))}
34 </div>
35 )
36}When NOT to Use
jsx
1// ❌ Premature optimization - simple operations
2const doubled = useMemo(() => count * 2, [count])
3
4// ❌ Functions that change every render anyway
5const handleSubmit = useCallback(() => {
6 submitForm(formData) // formData changes often
7}, [formData])
8
9// ❌ When child isn't memoized
10function Parent() {
11 const onClick = useCallback(() => {}, [])
12 return <Child onClick={onClick} /> // Child will re-render anyway!
13}Performance Guidelines
- Measure first - Don't optimize without profiling
- Use with React.memo - useCallback alone doesn't prevent re-renders
- Keep dependencies minimal - More deps = more recalculations
- Consider the cost - Memoization has its own overhead
jsx
1// Complete optimization pattern
2const MemoizedChild = React.memo(function Child({ data, onAction }) {
3 return <div onClick={() => onAction(data.id)}>{data.name}</div>
4})
5
6function Parent({ items }) {
7 const [selected, setSelected] = useState(null)
8
9 // Memoize processed data
10 const processedItems = useMemo(() =>
11 items.map(item => ({ ...item, formatted: format(item) })),
12 [items]
13 )
14
15 // Memoize handler
16 const handleAction = useCallback((id) => {
17 setSelected(id)
18 }, [])
19
20 return (
21 <div>
22 {processedItems.map(item => (
23 <MemoizedChild
24 key={item.id}
25 data={item}
26 onAction={handleAction}
27 />
28 ))}
29 </div>
30 )
31}These hooks are powerful tools for React performance optimization!
