Trap 2: an async handler sees the snapshot
After the await, qty is still the value from the click's render. The ref is one shared box, so it shows the newest value. Writing a ref during render is acceptable only for this "latest value" pattern; React 19.2's useEffectEvent is the cleaner tool inside effects.
Most hooks bugs come from three mistakes: a function that keeps reading values from an old render (a stale closure), an effect whose dependencies change every render (an effect loop), and an effect that sets something up without cleaning it up; each has a reliable fix.
Change the code and press Run (Ctrl+Enter). Predict what will happen first, then break it on purpose and read what changes. Your edits are saved and match the lesson page.
Ready for more? This phase has 6 hands-on exercises with automatic checks.
Practice questions
Write the code in the editor, run it, then open the model answer to compare.
Fix this so the countdown reaches 0: useEffect(() => { const id = setInterval(() => setLeft(left - 1), 1000); return () => clearInterval(id); }, []);
This effect loops. Fix it without disabling the lint rule: const filters = { tag }; useEffect(() => { setRows(applyFilters(all, filters)); }, [filters]);
Add the missing cleanup: useEffect(() => { document.addEventListener("keydown", onKey); }, [onKey]);
Explain it without notes
What is a stale closure in React? Give an example and three ways to fix it.
Why does an object in the dependency array cause an effect to run every render?
What does a missing effect cleanup cause, and how does StrictMode help you find it?
When would you use useEffectEvent instead of adding a value to the dependency array?
Preview
Console
Nothing logged yet.Expected console output
snapshot qty: 1
latest qty via ref: 3