Summary
Closures capture values from render when callback was created — effects and handlers see old state if deps wrong. useEffect missing deps → stale fetches; useCallback with empty deps → stale handlers. Functional updates (setState(prev => ...)) avoid stale state reads. ESLint exhaustive-deps catches many; understand why, don't blindly suppress.
Code example
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // stale — always sees initial count
}, 1000);
return () => clearInterval(id);
}, []); // fix: setCount(c => c + 1) or add [count]
return count;
}
Key points
- Closures capture values from render when callback was created — effects and handlers see old state if deps wrong.
useEffectmissing deps → stale fetches;useCallbackwith empty deps → stale handlers.- Functional updates (
setState(prev => ...)) avoid stale state reads. - ESLint exhaustive-deps catches many; understand why, don't blindly suppress.