fix: clear scroll-to-citation timers on panel close/unmount

Store setTimeout IDs in a ref and clear them when the source detail
panel closes or unmounts, preventing state updates on unmounted
components.

Fixes #1092
This commit is contained in:
Matt Van Horn 2026-04-15 22:00:56 -04:00
parent ff4e0f9b62
commit 008c464660
No known key found for this signature in database

View file

@ -130,6 +130,7 @@ export function SourceDetailPanel({
const t = useTranslations("dashboard");
const scrollAreaRef = useRef<HTMLDivElement>(null);
const hasScrolledRef = useRef(false); // Use ref to avoid stale closures
const scrollTimersRef = useRef<ReturnType<typeof setTimeout>[]>([]);
const [activeChunkIndex, setActiveChunkIndex] = useState<number | null>(null);
const [mounted, setMounted] = useState(false);
const [_hasScrolledToCited, setHasScrolledToCited] = useState(false);
@ -314,18 +315,22 @@ export function SourceDetailPanel({
const scrollAttempts = [50, 150, 300, 600, 1000];
scrollAttempts.forEach((delay) => {
setTimeout(() => {
scrollToCitedChunk();
}, delay);
scrollTimersRef.current.push(
setTimeout(() => {
scrollToCitedChunk();
}, delay)
);
});
// After final attempt, mark state as scrolled
setTimeout(
() => {
setHasScrolledToCited(true);
setActiveChunkIndex(citedChunkIndex);
},
scrollAttempts[scrollAttempts.length - 1] + 50
scrollTimersRef.current.push(
setTimeout(
() => {
setHasScrolledToCited(true);
setActiveChunkIndex(citedChunkIndex);
},
scrollAttempts[scrollAttempts.length - 1] + 50
)
);
}
},
@ -335,10 +340,16 @@ export function SourceDetailPanel({
// Reset scroll state when panel closes
useEffect(() => {
if (!open) {
scrollTimersRef.current.forEach(clearTimeout);
scrollTimersRef.current = [];
hasScrolledRef.current = false;
setHasScrolledToCited(false);
setActiveChunkIndex(null);
}
return () => {
scrollTimersRef.current.forEach(clearTimeout);
scrollTimersRef.current = [];
};
}, [open]);
// Handle escape key