The Problem

useCallback memoizes a function so it does not get recreated on every render. But if the dependency array is missing entries, the memoized function captures stale values from an earlier render.

Symptoms

  • Click handlers use old state values
  • Form submissions send outdated data
  • Callback works correctly the first time but fails on subsequent uses
  • No error in console, just silently wrong behavior

Real Error Scenario

```javascript function Counter() { const [count, setCount] = useState(0); const [step, setStep] = useState(1);

// BUG: Missing 'step' in dependency array const increment = useCallback(() => { console.log('Incrementing by', step); // Always logs 1! setCount(c => c + step); // Always adds 1! }, []); // Empty array = captures initial step=1 forever

return ( <div> <p>Count: {count}</p> <input type="number" value={step} onChange={e => setStep(Number(e.target.value))} /> <button onClick={increment}>Add</button> </div> ); } ```

No matter what value the user enters for step, the button always adds 1 because the memoized callback captured step = 1 from the first render.

How to Fix It

Fix 1: Add All Dependencies

javascript
const increment = useCallback(() => {
  console.log('Incrementing by', step);
  setCount(c => c + step);
}, [step]); // Now recreates when step changes

Fix 2: Functional Update When Possible

javascript
const increment = useCallback(() => {
  setCount(c => c + step); // Still needs step in deps
}, [step]);

Fix 3: useRef for Frequently Changing Values

```javascript function Counter() { const [count, setCount] = useState(0); const [step, setStep] = useState(1); const stepRef = useRef(step);

useEffect(() => { stepRef.current = step; }, [step]);

const increment = useCallback(() => { setCount(c => c + stepRef.current); // Always reads latest value }, []); // Stable callback, no recreation

// ... } ```

This keeps the callback stable (good for passing to memoized children) while still reading the latest step value.

Detection

Enable the ESLint rule to catch missing dependencies:

json
{
  "rules": {
    "react-hooks/exhaustive-deps": "warn"
  }
}

This will flag the empty dependency array:

bash
React Hook useCallback has a missing dependency: 'step'. Either include
it or remove the dependency array. (react-hooks/exhaustive-deps)