From 6f6be19f9d50bb01cd4dc44cdb24caa761a5dd75 Mon Sep 17 00:00:00 2001 From: mac-agent <91694179+mac-agent@users.noreply.github.com> Date: Sat, 4 Apr 2026 07:05:28 -0400 Subject: [PATCH] fix: clear scroll-to-citation setTimeouts on panel close/unmount in source-detail-panel Stagger setTimeout calls (50ms, 150ms, 300ms, 600ms, 1000ms) and the final state-update timer were never cleared when the panel was closed or unmounted, causing setState on unmounted component warnings. Added scrollTimersRef to track all timeout IDs and clear them when 'open' becomes false or on unmount. Fixes #1092 --- .../new-chat/source-detail-panel.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/surfsense_web/components/new-chat/source-detail-panel.tsx b/surfsense_web/components/new-chat/source-detail-panel.tsx index bff088971..12db4cd9d 100644 --- a/surfsense_web/components/new-chat/source-detail-panel.tsx +++ b/surfsense_web/components/new-chat/source-detail-panel.tsx @@ -274,6 +274,9 @@ export function SourceDetailPanel({ [shouldReduceMotion] ); + // Track stagger scroll timers so they can be cleared on close/unmount + const scrollTimersRef = useRef[]>([]); + // Callback ref for the cited chunk - scrolls when the element mounts const citedChunkRefCallback = useCallback( (node: HTMLDivElement | null) => { @@ -312,32 +315,41 @@ export function SourceDetailPanel({ // Each subsequent scroll will correct for any layout shifts const scrollAttempts = [50, 150, 300, 600, 1000]; + // Track all timeout IDs so they can be cleared on close/unmount scrollAttempts.forEach((delay) => { - setTimeout(() => { + const id = setTimeout(() => { scrollToCitedChunk(); }, delay); + scrollTimersRef.current.push(id); }); // After final attempt, mark state as scrolled - setTimeout( + const doneTimer = setTimeout( () => { setHasScrolledToCited(true); setActiveChunkIndex(citedChunkIndex); }, scrollAttempts[scrollAttempts.length - 1] + 50 ); + scrollTimersRef.current.push(doneTimer); } }, [open, citedChunkIndex] ); - // Reset scroll state when panel closes + // Clear scroll timers and 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