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.
This commit is contained in:
Rocky
2026-05-01 04:18:39 +05:30
committed by GitHub
parent 2166d04a20
commit 42e7de96fa

View File

@@ -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 (