mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-05-07 10:08:50 -05:00
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:
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user