💡THE BIG IDEA
useEffect’s three mysteries — why the effect doesn’t run during render, when it actually runs, and why cleanup remembers OLD values — are all one mechanism: a queue that waits for paint, and closures frozen per render. We build it, run it, and watch a chat app switch rooms in exactly the order real React uses.
1const effects = [];
2function useEffect(fn) { effects.push(fn); }
3let cleanup = null;
4function Chat({ room }) {
5 useEffect(() => {
6 console.log("connect to", room);
7 return () => console.log("disconnect from", room);
8 });
9 console.log("render", room);
10}
11function commit(room) {
12 Chat({ room });
13 console.log("(screen painted)");
14 if (cleanup) cleanup();
15 cleanup = effects.pop()();
16}
17commit("maths");
18commit("physics");
●A chat component connects to a room. You switch from "maths" to "physics" — and somehow React knows to disconnect from maths first, at exactly the right moment. Who taught it that?
We build useEffect’s scheduling in 18 lines that really run. Three ghosts die today: "why doesn’t my effect run during render?", "when exactly does it run?", and "why does cleanup remember the OLD room?"
📦 Memory boxes
what the program is remembering right now
nothing remembered yet🖥️ What the computer shows
the answers the program prints out
nothing yet ▌