What is a stale closure in a React effect, and how do you fix it?
A closure captures the values from the render where it was created. An effect with a missing dependency keeps using old values. Fix it by listing the dependency, using the updater form of setState, or storing the latest value in a ref.
Explain it simply
Each render has its own copy of props and state, like a separate photocopy of a form. An effect created in render 1 holds the photocopy of render 1. If the effect never re-runs, it keeps reading old data even though newer renders have newer values.
The classic example is a timer that always shows the same number. The interval callback was created when n was 0, so it computes 0 + 1 forever.
There are three fixes. Add the value to the dependency array so the effect is recreated (and clean up the old one). Use the updater form setN(x => x + 1) so the callback does not need n at all. Or keep the latest value in a ref when you need one long-lived callback that always sees fresh data.
Example
function Counter() {
const [n, setN] = useState(0);
useEffect(() => {
const id = setInterval(() => setN(n + 1), 1000); // n is always 0 here
return () => clearInterval(id);
}, []); // so the display sticks at 1
// Fix: no dependency on n needed
useEffect(() => {
const id = setInterval(() => setN(x => x + 1), 1000);
return () => clearInterval(id);
}, []);
return <p>{n}</p>;
}What to say in the interview
"A stale closure happens when a callback or effect captures old render values. I fix it by adding the real dependencies, using functional updates when I only need the previous state, or reading the latest value from a ref when I need a stable callback."
Common mistakes
- Silencing the exhaustive-deps warning instead of fixing the closure.