From e5cabf95e46f75854d56a5ca6eb2315ccce9752b Mon Sep 17 00:00:00 2001 From: likiosliu Date: Wed, 25 Mar 2026 12:34:30 +0800 Subject: [PATCH] fix: clean up recursive setTimeout calls in onboarding tour - Add cancelled flag to prevent state updates after unmount in checkAndStartTour retry loop - Store retry timer ID in a ref and clear it on cleanup in updateTarget effect Closes #950 --- surfsense_web/components/onboarding-tour.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/surfsense_web/components/onboarding-tour.tsx b/surfsense_web/components/onboarding-tour.tsx index 03fad87b6..114a46141 100644 --- a/surfsense_web/components/onboarding-tour.tsx +++ b/surfsense_web/components/onboarding-tour.tsx @@ -436,6 +436,7 @@ export function OnboardingTour() { const { resolvedTheme } = useTheme(); const pathname = usePathname(); const retryCountRef = useRef(0); + const retryTimerRef = useRef | null>(null); const maxRetries = 10; // Track previous user ID to detect user changes const previousUserIdRef = useRef(null); @@ -477,7 +478,7 @@ export function OnboardingTour() { retryCountRef.current = 0; } else if (retryCountRef.current < maxRetries) { retryCountRef.current++; - setTimeout(() => { + retryTimerRef.current = setTimeout(() => { const retryEl = document.querySelector(currentStep.target); if (retryEl) { setTargetEl(retryEl); @@ -487,6 +488,10 @@ export function OnboardingTour() { } }, 200); } + + return () => { + if (retryTimerRef.current) clearTimeout(retryTimerRef.current); + }; }, [currentStep]); // Check if tour should run: localStorage + data validation with user ID tracking @@ -556,7 +561,11 @@ export function OnboardingTour() { } // User is new and hasn't seen tour - wait for DOM elements and start tour + let cancelled = false; + const checkAndStartTour = () => { + if (cancelled) return; + // Check if all required elements exist const connectorEl = document.querySelector(TOUR_STEPS[0].target); const documentsEl = document.querySelector(TOUR_STEPS[1].target); @@ -578,7 +587,10 @@ export function OnboardingTour() { // Start checking after initial delay const timer = setTimeout(checkAndStartTour, 500); - return () => clearTimeout(timer); + return () => { + cancelled = true; + clearTimeout(timer); + }; }, [mounted, user?.id, searchSpaceId, pathname, threadsData, documentTypeCounts, connectors]); // Update position on resize/scroll