Hooks
useCallback
Master the useCallback Hook, covering function definition caching, reference equality, child re-render optimizations, and comparisons with useMemo.
1. Learning Objectives
In this lesson, you will learn how to optimize rendering performance by caching function definitions. By the end of this topic, you will be able to:
- Differentiate between caching a function's return value (
useMemo) and the function definition itself (useCallback). - Apply the
useCallbackHook to keep function references stable between renders. - Prevent unnecessary re-renders of child components wrapped in
React.memo. - Configure correct dependency arrays to prevent stale closures in callbacks.
- Identify when wrapping callbacks adds unnecessary overhead.
2. Overview
The useCallback Hook is a React optimization tool that caches a function definition between renders. In JavaScript, functions are objects, and declaring a function inside a component creates a new reference on every render. useCallback keeps this reference stable, preventing child components that receive the function as a prop from re-rendering unnecessarily.
3. Why This Topic Matters
Stable function references are crucial for child component optimizations:
- Downstream Re-renders: If you pass an inline function (e.g.
onClick={() => handleClick(id)}) to a child component, the child will re-render on every parent render, even if it is wrapped inReact.memo, because the function reference is always new. - Dependency Loops: If a callback function is read inside a
useEffectHook in a child component, passing a new function reference on every render causes the effect to run repeatedly, potentially creating infinite loops.
4. Real-World Analogy
Think of useCallback like **a company distributing business cards**:
- Without useCallback: Reprinting your business cards (recreating the function) on every single client meeting, even if your phone number and email address did not change. This wastes paper and money.
- With useCallback: Reusing the same stack of printed cards (the cached function reference) across meetings. You only reprint the cards if your contact details change (dependencies update).
5. Core Concepts
Below is a comparison of the useMemo and useCallback Hooks:
| Feature | useMemo | useCallback |
|---|---|---|
| What it caches | The *return value* of a calculation function. | The *function definition itself* (without executing it). |
| Primary Use Case | Caching expensive calculations (sorting, filtering). | Caching event handlers passed to memoized child components. |
| Equivalence | useMemo(() => fn, [deps]) |
Equivalent to: useMemo(() => () => fn(), [deps]) |
6. Syntax & API Reference
This example shows how to use `useCallback` to keep a function reference stable:
7. Visual Diagram
This diagram displays how stable callback references prevent optimized child components from re-rendering:
8. Live Example — Full Working Code
Below is a complete HTML page demonstrating child re-render prevention using `useCallback` and `React.memo`:
What just happened? Opening the console shows that clicking "Toggle" does not trigger any re-renders for the ListItem components. This is because handleDelete is wrapped in useCallback, ensuring its reference remains identical between parent renders. When combined with React.memo, this allows the child components to skip re-rendering.
9. Interactive Playground
Try It Yourself Challenges:
- Remove
useCallbackfrom thehandleDeletedeclaration (declaring it as a standard function) and verify that clicking the toggle button now triggers child re-renders. - Add a new input text field, and verify that the memoized children skip re-renders while the user is typing.
10. Common Mistakes
| Mistake | Why it happens | Wrong | Correct |
|---|---|---|---|
| Wrapping inline callbacks passed to standard elements | Wrapping functions passed to built-in HTML tags (like <button>) in useCallback is redundant. Standard DOM tags do not benefit from reference checks, so this wrapper simply adds memory overhead. |
<button onClick={useCallback(fn, [])}> |
<button onClick={fn}> (pass directly) |
| Creating stale closures by omitting dependencies | If you read variables (like state or props) inside a callback but omit them from the dependency array, the Hook will continue to return a cached function that reference old values, causing bugs. | useCallback(() => console.log(val), []) (val is omitted) |
useCallback(() => console.log(val), [val]) |
11. Best Practices
- Only memoize when necessary: Only use `useCallback` when passing functions to optimized child components wrapped in `React.memo`, or when the function is a dependency of another Hook (like `useEffect`).
- Always match dependencies: List all variables referenced inside the callback in its dependency array to prevent stale closures.
- Use functional state updates: If your callback only updates state based on its previous value, use the updater function syntax (`setCount(prev => prev + 1)`) to avoid having to list the state variable as a dependency, keeping the function reference stable.
12. Browser Compatibility/Requirements
The useCallback Hook is supported in all browsers that run React.
13. Interview Questions
Q1: What is the main difference between `useMemo` and `useCallback`?
Answer: Both are optimization Hooks, but they cache different things: (1) `useMemo` executes the function and caches its **return value** (like a sorted array), whereas (2) `useCallback` caches the **function definition itself** without executing it, keeping the function reference stable across renders.
Q2: Why does wrapping a parent component function in `useCallback` have no effect if the child component is not wrapped in `React.memo`?
Answer: By default, React re-renders all child components whenever a parent component renders, regardless of whether their props have changed. If the child is not optimized with `React.memo`, it will re-render anyway. `useCallback` only improves performance when the child uses reference checks (like `React.memo`) to skip rendering when props are identical.
14. Debugging Exercise
Identify why this input search handler returns incorrect filtering results on keystrokes:
Diagnosis: The dependency array is empty, causing `useCallback` to cache a function definition that references the initial empty `query` value (`''`). Because of this stale closure, subsequent calls to the function continue to send empty search parameters to the API.
Fix: Add query to the dependency array:
15. Practice Exercises
Exercise 1: Create a button list panel
Build a component named ActionGrid. Declare callback handlers for buttons using useCallback, passing them to memoized button elements to prevent child re-renders on parent state changes.
16. Scenario-Based Challenge
The High-Frequency Event Debounce Callback:
You are designing a window scroll logger that sends event metrics to a server. To prevent overload, you decide to debounce the scroll handler. Explain how you would use useCallback to keep the debounced function reference stable, preventing the scroll handler from resetting on every render.
17. Quick Quiz
Q1: Which API is used to optimize child functional components by skipping rendering when incoming props are identical?
A) React.useMemo
B) React.memo
C) React.useCallback
Answer: B — React.memo is a higher-order component used to optimize functional components by skipping rendering if props are identical.
18. Summary & Key Takeaways
- The
useCallbackHook caches function definitions between renders. - Use `useCallback` to prevent child components optimized with `React.memo` from re-rendering due to changing function references.
- Ensure all state, props, or variables referenced inside the callback are listed in the dependency array.
- Use functional updates to keep callback function references stable over time.
19. Cheat Sheet
| useCallback Pattern | Purpose |
|---|---|
const fn = useCallback(() => { ... }, [dep]) |
Caches a function definition, updating it only when the dependency values change. |
const fn = useCallback(() => { ... }, []) |
Caches the function definition permanently (stable reference). |
const Child = React.memo(MyComponent); |
Optimizes a child component by skipping rendering if props are identical. |