A stale closure in an effect, and the two ways out

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);
}, []);

// 1. the functional setter, for updates
setCount(c => c + 1);

// 2. a ref, for reads
const latest = useRef(count);
useEffect(() => { latest.current = count; });

A closure is a snapshot and every render creates new ones, which is the single most confusing thing about hooks for anyone arriving from classes where this.state is always current. The functional setter is the idiomatic fix for updates; the ref is the escape hatch for reads. Reaching for the ref first tends to produce code that works and cannot be reasoned about.