useCallback and React.memo are performance optimization tools. They are commonly used together when a parent component renders frequently and a child component can avoid unnecessary work when its inputs have not changed.
10.1 What Problem Are They Solving?
When a component renders, JavaScript creates new function values for functions declared during that render. If such a function is passed to a memoized child, the child can see a new function reference even when the function's behavior has not meaningfully changed.
useCallback can preserve the function reference between renders when its dependencies have not changed. React.memo can skip a child render when its props are considered unchanged.
10.2 React.memo
React.memo creates a memoized component. When its parent renders, React can skip rendering the memoized child if its props have not changed according to the comparison used by the memoized component.
const UserCard = React.memo(function UserCard({ name }) {
console.log("UserCard rendered");
return <div>{name}</div>;
});
Memoization is an optimization. It should not be required for the component to be logically correct.
10.3 Why Function Props Can Break Memoization
function Parent() {
const [count, setCount] = useState(0);
function handleSelect() {
console.log("Selected");
}
return (
<>
<button onClick={() => setCount(count + 1)}>
{count}
</button>
<Child onSelect={handleSelect} />
</>
);
}
On each parent render, handleSelect is a new function object. If Child is memoized, its function prop can therefore appear changed even when the logic is equivalent.
10.4 useCallback
useCallback caches a function definition between renders until one of its dependencies changes.
const handleSelect = useCallback(() => {
console.log("Selected");
}, []);
Now the function reference can remain stable across renders while the dependency list remains unchanged.
10.5 useCallback + React.memo
const Child = React.memo(function Child({ onSelect }) {
console.log("Child rendered");
return (
<button onClick={onSelect}>
Select
</button>
);
});
function Parent() {
const [count, setCount] = useState(0);
const handleSelect = useCallback(() => {
console.log("Selected");
}, []);
return (
<>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<Child onSelect={handleSelect} />
</>
);
}
When the parent count changes, the parent renders again. Because handleSelect retains its reference and Child's other props remain unchanged, the memoized Child may be able to skip its render.
10.6 Performance Flow
flowchart TD
A[Parent State Changes] --> B[Parent Renders]
B --> C{Child Props Changed?}
C -->|No| D[React.memo Can Skip Child Render]
C -->|Yes| E[Child Renders]
B --> F[useCallback Checks Dependencies]
F --> G[Stable Function Reference If Dependencies Unchanged]
10.7 Dependency Changes
If a callback depends on a value, that value belongs in the dependency list.
const handleSelect = useCallback(() => {
console.log(userId);
}, [userId]);
When userId changes, React can create the callback for the new dependency value.
10.8 Functional Updates Can Reduce Dependencies
Sometimes a callback can use a functional state update instead of reading the current state value directly.
const increment = useCallback(() => {
setCount(previous => previous + 1);
}, []);
This callback does not need the current count value because React provides the previous state to the updater.
10.9 React.memo Is Not Deep Comparison
Memoized components generally rely on comparison of prop values using shallow/reference equality semantics. A new object or function reference can therefore be considered changed even if its contents look identical.
const options = { pageSize: 20 };
<Child options={options} />
If options is recreated on every parent render, the child receives a new object reference. Depending on the design, stable values can be created outside the component or memoized appropriately.
10.10 When Not to Use useCallback
Adding useCallback everywhere can make code more complex without producing a meaningful performance improvement. It is most useful when function identity matters, for example when passing a callback to a memoized child or when a callback is a dependency of another Hook and stable identity is useful.
10.11 When Not to Use React.memo
If a component is inexpensive to render or its props change frequently, memoization may provide little benefit. A component should not be wrapped with React.memo automatically simply because performance is important.
10.12 useCallback vs useMemo
| Concept | useCallback | useMemo |
|---|---|---|
| What is cached? | Function reference | Calculation result |
| Typical purpose | Stable callback identity | Avoid repeated expensive calculation |
| Returns | Function | Calculated value |
| Common pairing | React.memo | Memoized derived data |
10.13 React.memo vs useCallback
| Tool | Optimization Target |
|---|---|
| React.memo | Component rendering based on props. |
| useCallback | Function reference identity. |
| useMemo | Calculated value identity/result. |
10.14 Common Mistakes
- Assuming
React.memoprevents every child render. - Using
useCallbackfor every function without a performance reason. - Leaving dependencies out of
useCallback. - Forgetting that new object and array references can also cause memoized children to render.
- Thinking memoization changes application behavior rather than optimizing it.
- Optimizing before identifying an actual rendering or calculation bottleneck.
10.15 Interview Questions
- What is
React.memo? - What problem does
useCallbacksolve? - Why can a normal function prop cause a memoized child to render?
- How do
useCallbackandReact.memowork together? - What happens when a callback dependency changes?
- What is the difference between
useCallbackanduseMemo? - Does
React.memoperform a deep comparison of every prop? - Can
useCallbackmake performance worse? - When should you avoid memoization?
- How can functional state updates reduce callback dependencies?
10.16 Key Takeaways
React.memocan skip rendering a component when its props have not changed.useCallbackcan keep a callback reference stable between renders.- They are often useful together when function props are passed to memoized children.
- Dependencies must accurately represent values used by callbacks.
- Memoization is an optimization, not a correctness requirement.
- Use profiling and actual performance needs to guide optimization.
Conclusion
useCallback and React.memo are easiest to understand when you focus on reference identity. React.memo optimizes component rendering based on props, while useCallback can stabilize a function prop. Used appropriately, they can reduce unnecessary work; used everywhere without a reason, they can add complexity without meaningful benefit.