🎓 What You Will Learn
- What useCallback does: Memoize callback functions to prevent unnecessary re-renders
- When to use it: Optimize performance when passing functions to optimized child components
- Dependency arrays: How dependencies control when functions are recreated
- Performance impact: See real examples of how useCallback prevents child re-renders
- Common patterns: useCallback with parameters, closures, and event handlers
- When NOT to use it: Avoid premature optimization and unnecessary memoization
What is useCallback?
useCallback is a React Hook that memoizes a callback function. It returns the same function object between re-renders, unless its dependencies have changed.
Think of it as "Remember this function". Instead of creating a new function every render, React returns the same function object, preventing child components from re-rendering unnecessarily.
Basic Syntax
The syntax is straightforward:
import { useCallback } from 'react';
function Component() {
// Memoize a callback function
const handleClick = useCallback(() => {
console.log('Button clicked!');
}, []); // Empty dependency array
return <button onClick={handleClick}>Click me</button>;
}The second argument is a dependency array. When dependencies change, a new function is created. When nothing changes, the old function is reused.
Without vs With useCallback
See the difference between creating new functions every render vs memoizing with useCallback:
▶ Try it Yourself
Click the button. Every time the parent re-renders, a new function object is created, causing the child to re-render unnecessarily.
Count: 0 | Parent renders: 0
Child re-renders: 0
React.memo to prevent re-renders from props comparison.Understanding Dependencies
The dependency array controls when a new function is created. If any dependency changes, useCallback creates a new function object.
| Dependency Array | Function Behavior |
|---|---|
| [] | Function created once, never updates (not recommended for most cases) |
| [variable] | Function recreated when 'variable' changes |
| [var1, var2] | Function recreated when either variable changes |
| No array | Creates new function every render (defeats purpose) |
▶ Try it Yourself
When dependencies change, useCallback creates a new function. Try changing the multiplier!
Count: 0
multiplier, include it in dependencies!useCallback with Function Parameters
Memoized callbacks can accept parameters just like normal functions. This is useful for list items, dynamic handlers, or event callbacks.
▶ Try it Yourself
useCallback functions can accept parameters. Use them like normal functions.
- React
- JavaScript
- TypeScript
useCallback with parameters for event handlers, filters, and dynamic operations.Inline vs useCallback
Sometimes an inline function is perfectly fine. useCallback is useful for performance-critical scenarios, but not always necessary.
▶ Try it Yourself
Count: 0
useCallback when passing functions to optimized child components (wrapped with React.memo).When to Use useCallback
Use useCallback in these scenarios:
- Child component wrapped with React.memo: Prevents unnecessary child re-renders
- Functions as dependencies: When a callback is a dependency of another hook (useEffect)
- Heavy computations: When creating the function is expensive
- Optimized lists: When rendering large lists with callbacks
useCallback everywhere. Measure performance first with React DevTools Profiler before optimizing.Closures and useCallback
Memoized functions capture variables from their scope. This is called a closure. Be careful with stale closures!
import { useState, useCallback } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// WRONG: count is captured at function creation time
const handleClickWrong = useCallback(() => {
alert('Count is ' + count); // Always shows old count!
}, []); // Missing count dependency!
// RIGHT: count is included as dependency
const handleClickRight = useCallback(() => {
alert('Count is ' + count); // Shows current count
}, [count]); // Include count
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={handleClickWrong}>Alert (Wrong)</button>
<button onClick={handleClickRight}>Alert (Right)</button>
<p>Count: {count}</p>
</div>
);
}useCallback vs useMemo
Both are memoization hooks, but they memoize different things:
| Hook | Memoizes | Use Case |
|---|---|---|
| useCallback | Functions | Prevent child re-renders, function dependencies |
| useMemo | Values | Expensive computations, derived values |
| useCallback(() => 5, []). | Is same as useMemo(() => () => 5, []). | In practice, they solve different problems |
import { useState, useCallback, useMemo } from 'react';
function Data() {
const [count, setCount] = useState(0);
// useCallback: memoize a function
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
// useMemo: memoize a computed value
const expensiveValue = useMemo(() => {
return heavyComputation(count);
}, [count]);
return (
<div>
<button onClick={handleClick}>Increment</button>
<p>Result: {expensiveValue}</p>
</div>
);
}Best Practices & Tips
- Include all variables used in the callback as dependencies
- Use with
React.memofor optimized children - Measure performance impact with React DevTools Profiler
- Use ESLint plugin
eslint-plugin-react-hooksto catch dependency mistakes
- Use
useCallbackfor simple inline handlers - Forget dependencies in the array
- Optimize before measuring performance
- Assume every child needs a memoized callback
What's Next
- useMemo: Memoize expensive computations
- React.memo: Prevent component re-renders
- Performance optimization: Profile and identify bottlenecks
- useReducer: Manage complex state logic with callbacks
Now that you understand useCallback, you're ready to optimize React applications for better performance. Remember: measure first, optimize second!