Hint
A closure captures variables at creation time — if state changes but the closure isn't recreated, it reads old values
A stale closure occurs when a function captures a value from a previous render and never gets updated to see the new value.
// ❌ Classic example — setInterval with stale count
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
console.log(count); // always logs 0 — stale closure!
setCount(count + 1); // always sets to 1 — bug!
}, 1000);
return () => clearInterval(id);
}, []); // empty deps — closure captures count=0 forever
}
// ✅ Fix 1 — functional update (doesn't need to close over count)
setCount(c => c + 1); // always gets the latest count
// ✅ Fix 2 — add count to deps (effect re-runs on every count change)
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id); // cleans up old interval
}, [count]);
// ✅ Fix 3 — useRef stores mutable value, not affected by closures
const countRef = useRef(count);
useEffect(() => { countRef.current = count; }, [count]);
useEffect(() => {
const id = setInterval(() => {
setCount(countRef.current + 1); // always reads latest
}, 1000);
return () => clearInterval(id);
}, []);