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
const increment = useCallback(() => {
console.log('Incrementing by', step);
setCount(c => c + step);
}, [step]); // Now recreates when step changesFix 2: Functional Update When Possible
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:
{
"rules": {
"react-hooks/exhaustive-deps": "warn"
}
}This will flag the empty dependency array:
React Hook useCallback has a missing dependency: 'step'. Either include
it or remove the dependency array. (react-hooks/exhaustive-deps)