When to use
- Passing callbacks to memoized children.
- Returning functions from custom hooks.
- Keeping a function dependency stable for an effect.
- Debugging avoidable renders caused by changing function references.
Goal
Use useCallback only for needed function identity stability.
Do not add it by default.
Rules
- Use with
memo()children when callback identity matters. - Wrap functions returned from custom hooks.
- Prefer moving effect-only functions inside the effect.
- Use updater functions to remove state dependencies.
- Keep dependency arrays complete.
- Do not use inside loops.
- Do not use when the child is not memoized.
- Do not use for simple interactions without evidence.
Good Uses
Memoized child:
const handleClick = useCallback(() => {
trackClick(productId);
}, [productId]);
Custom hook return value:
const navigate = useCallback((url) => {
dispatch({ type: "navigate", url });
}, [dispatch]);
Updater dependency reduction:
const handleAddTodo = useCallback((text) => {
setTodos((todos) => [...todos, { id: nextId++, text }]);
}, []);
Prefer Restructure
- Move effect-only helpers inside the effect.
- Keep state local before memoizing props.
- Accept JSX as
childrenwhen wrapper state should not rerender children. - Extract loop items into components instead of calling hooks in loops.
Debug
- Confirm child is wrapped in
memo(). - Log dependency arrays across renders.
- Check unstable object or array dependencies.
- Remove unnecessary lifted state before adding memoization.
Output
- Keep, add, or remove
useCallback. - Reason tied to identity stability.
- Complete dependency array.
- Simpler restructure when better than memoization.