performance

useCallback

Returns a memoized version of a callback whose identity stays stable until a dependency changes. Reach for it when passing callbacks to memoized children (React.memo) or as dependencies of other hooks that would otherwise re-run on every render.

const memoized = useCallback(fn, [deps])

Parameters & Behavior

Parameter Purpose
fn The function to memoize
dependencies Recreate the function only when any dep changes
equivalent to useMemo useCallback(fn, deps) === useMemo(() => fn, deps)
stable identity Prevents child re-renders when the child is wrapped in React.memo

Examples

const handleClick = useCallback(() => setCount(c => c + 1), []);
return <ExpensiveChild onClick={handleClick} />;

Stable handler lets React.memo(ExpensiveChild) skip re-rendering

const load = useCallback(() => fetch(`/api/${id}`).then(r => r.json()), [id]);
useEffect(() => { load().then(setData); }, [load]);

Include a memoized callback in another effect's deps safely

const onSelect = useCallback((row) => setSelectedId(row.id), []);

Row handlers stay identical across renders — big win in virtualized tables

const debouncedSearch = useCallback(debounce((q) => search(q), 300), [search]);

Keeps the same debounced instance until `search` changes

Gotcha

Wrapping every function is cargo culting — it costs memory and only pays off with a memoized consumer or a hook dep. Missing deps produce stale closures the same way useEffect does.

Related hooks

← All React hooks · Fix hydration mismatch