A Function That Keeps Its Identity Across Renders
useCallback
`useCallback(fn, deps)` is `useMemo` for functions — same fn reference as long as deps don't change.
What you'll learn
- Stabilize a callback's identity
- Recognize when `useCallback` matters
Defining a function inside a component creates a brand-new function
every render. Usually that’s fine. Sometimes it matters — when
that function is in another hook’s deps, or passed as a prop to a
memo-wrapped child.
useCallback(fn, deps) keeps the same reference until deps
change.
The Shape
const onSave = useCallback(() => {
send(form);
}, [form]); It’s literally useMemo(() => fn, deps). Same semantics, just a
shortcut for functions.
When It Actually Helps
Stabilizing a dep
function Search({ id }) {
const fetchUser = useCallback(() => {
return fetch(`/api/users/${id}`).then(r => r.json());
}, [id]);
useEffect(() => {
fetchUser().then(setData);
}, [fetchUser]); // re-runs only when id changes
} Without useCallback, fetchUser is new every render, the effect
sees a “new” dep every render, and it runs in a loop.
Passing to a memo’d child
const SaveButton = memo(function SaveButton({ onClick }) {
return <button onClick={onClick}>Save</button>;
});
function Form() {
const onSave = useCallback(() => { ... }, []);
return <SaveButton onClick={onSave} />;
} Without useCallback, every render passes a new onClick, so the
memo wrapper can’t skip — the child re-renders too.
When It’s Pointless
Wrapping every function in useCallback is a common over-correction.
If the function is only passed to a plain DOM <button>, identity
doesn’t matter. The button doesn’t re-render based on whether
onClick is a new function — it just calls whatever you give it.
| Function passed to… | useCallback worth it? |
|---|---|
A plain <div onClick={fn}> | No |
| Another hook’s deps | Yes |
A memo-wrapped child | Yes |
| Just used inside the component | No |
Same Deps Rules
Everything reactive that the function reads should be in deps.
ESLint enforces this.
Up Next
Sharing data across components without prop drilling.
useContext →