iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

useCallback

useCallback returns a memoised function reference that only changes when its dependencies change. Pair with React.memo children or effect dependencies — not by default.

When useCallback actually helps

EXAMPLE
import { useCallback, useState, memo } from 'react';

// 1) Stable handler for a memoised child
const HeavyChild = memo(function HeavyChild({ onChange }) {
    /* expensive render */
    return <input onChange={onChange} />;
});

function Parent() {
    const [count, setCount] = useState(0);

    // Without useCallback, onChange is a NEW function each render → HeavyChild rerenders
    const onChange = useCallback((e) => {
        console.log(e.target.value);
    }, []);

    return (
        <>
            <button onClick={() => setCount(c => c + 1)}>+1 {count}</button>
            <HeavyChild onChange={onChange} />
        </>
    );
}

// 2) Stable function in useEffect deps
function Searcher({ query }) {
    const [results, setResults] = useState([]);

    const fetchResults = useCallback(async () => {
        const r = await api.search(query);
        setResults(r);
    }, [query]);

    useEffect(() => { fetchResults(); }, [fetchResults]);
    return <List items={results} />;
}

// 3) Passing handlers down through context
const ToggleCtx = createContext();
function Provider({ children }) {
    const [on, setOn] = useState(false);
    const toggle = useCallback(() => setOn(o => !o), []);
    return <ToggleCtx.Provider value={{ on, toggle }}>{children}</ToggleCtx.Provider>;
}

// 4) When NOT to use useCallback
//   • The child is NOT React.memo — fresh function refs don't cause perf issues
//   • The handler is recreated every render anyway because deps change every render
//   • Inline handlers in simple components — useCallback's overhead > savings

// 5) Common bugs

// 5a) Empty deps + closure over state — STALE
const increment = useCallback(() => setCount(count + 1), []);
// Reads count from initial render forever. Fix:
const increment2 = useCallback(() => setCount(c => c + 1), []);

// 5b) Forgetting a dep
const submit = useCallback(() => api.post(formData), []);   // formData stale
const submit2 = useCallback(() => api.post(formData), [formData]);

// 5c) Wrapping unstable callbacks in useCallback that depends on them
const stable = useCallback((id) => onSelect(id), [onSelect]);
// If onSelect changes every render, stable changes every render — pointless.

// 6) useCallback vs useMemo
//   useMemo(() => computeFn(), [deps])  — memoise the RESULT of calling fn
//   useCallback(fn, [deps])              — memoise the FUNCTION reference
//   useCallback(fn, deps) === useMemo(() => fn, deps)

// 7) Profile FIRST
// React DevTools Profiler will show which children are re-rendering and why.
// Most apps DON'T need useCallback; over-using it adds noise without speed.

Why it matters

useCallback only helps when its returned function is consumed by something that cares about reference equality — a memo child, a useEffect dep array, or a context value. Otherwise it’s noise.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
const onClick = useCallback(() => save(id), [id]);
return <Button onClick={onClick} />;
Try it Yourself »

Discussion

Loading…