From 42e7de96fa8c6b689527d2ccfb4e67d2a79ee376 Mon Sep 17 00:00:00 2001 From: Rocky Date: Fri, 1 May 2026 04:18:39 +0530 Subject: [PATCH] fix(staffml): add pickRandom to keyboard effect and pickNext dep arrays (#1602) The keyboard shortcut useEffect captured a stale pickRandom closure because pickRandom was not in its dependency array. When a user changed a filter, pool updated and pickRandom got a new identity via useCallback, but the keyboard handler kept firing the old closure. Pressing N after a filter change would pick from the pre-filter pool. Same stale-closure bug existed in pickNext, which calls pickRandom but only listed [reviewMode, pool] as deps. Fix: move pickRandom before the keyboard effect (satisfies the before-use declaration order) and add it to both dep arrays. --- interviews/staffml/src/app/practice/page.tsx | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/interviews/staffml/src/app/practice/page.tsx b/interviews/staffml/src/app/practice/page.tsx index c83aa8ea5..4e1637c31 100644 --- a/interviews/staffml/src/app/practice/page.tsx +++ b/interviews/staffml/src/app/practice/page.tsx @@ -393,6 +393,22 @@ function PracticePage() { setNapkinResult(null); }, [mounted, selectedTrack, selectedLevel, selectedArea, selectedZone, napkinOnly, chainsOnly, visualOnly]); + const pickRandom = useCallback((fromPool?: Question[]) => { + // Track skip if there was a current question that wasn't scored + if (current && !showAnswer) { + track({ type: 'question_skipped', topic: current.topic, level: current.level }); + } + const p = fromPool || pool; + if (p.length === 0) return; + const idx = Math.floor(Math.random() * p.length); + setCurrent(p[idx]); + questionShownAt.current = Date.now(); + setShowAnswer(false); + setUserAnswer(""); + setNapkinResult(null); + setRubricItems([]); + }, [pool, current, showAnswer]); + // Keyboard shortcuts: Enter to reveal, 1-4 for scoring, N to skip useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -413,23 +429,7 @@ function PracticePage() { }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); - }, [showAnswer, current]); - - const pickRandom = useCallback((fromPool?: Question[]) => { - // Track skip if there was a current question that wasn't scored - if (current && !showAnswer) { - track({ type: 'question_skipped', topic: current.topic, level: current.level }); - } - const p = fromPool || pool; - if (p.length === 0) return; - const idx = Math.floor(Math.random() * p.length); - setCurrent(p[idx]); - questionShownAt.current = Date.now(); - setShowAnswer(false); - setUserAnswer(""); - setNapkinResult(null); - setRubricItems([]); - }, [pool, current, showAnswer]); + }, [showAnswer, current, pickRandom]); // Submit-gradient guard: intercept reveals that look like // "didn't really try." The restructured layout puts the Reveal @@ -578,7 +578,7 @@ function PracticePage() { setReviewMode(false); } pickRandom(); - }, [reviewMode, pool]); + }, [reviewMode, pool, pickRandom]); if (!mounted) { return (