An effect with an empty dependency array captures the first render’s variables permanently, so a timer set up once reads values that stopped being current seconds later.
// count is always 0 inside the interval
useEffect(() => {
const t = setInterval(() => console.log(count), 1000);
return () => clearInterval(t);
}, []);
// the functional setter reads the current value without a dependency
setCount(c => c + 1);
// or a ref, when the value is only read
const latest = useRef(count);
useEffect(() => { latest.current = count; });
This is the single most confusing thing about hooks for anyone coming from classes, where this.state is always current because this is a mutable object. A closure is a snapshot, and every render creates new ones. The functional setter is the idiomatic fix for updates; the ref is the escape hatch for reads, and reaching for the ref first tends to produce code that works and cannot be reasoned about.