From 8a8e5fcd76c5dcebb6fbc2142212408553ee7d6d Mon Sep 17 00:00:00 2001 From: SohamBhattacharjee2003 <125297948+SohamBhattacharjee2003@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:29:41 +0530 Subject: [PATCH] fix(hooks): add AbortController to properly cancel fetch requests on unmount --- package-lock.json | 6 ++++++ .../components/circleback-config.tsx | 16 ++++++++++++---- .../components/editor-panel/editor-panel.tsx | 18 ++++++++---------- .../layout/ui/tabs/DocumentTabContent.tsx | 18 ++++++++---------- 4 files changed, 34 insertions(+), 24 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..9703ac09f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "SurfSense", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/circleback-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/circleback-config.tsx index 99e26c542..268ab0f98 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/circleback-config.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/circleback-config.tsx @@ -34,9 +34,12 @@ export const CirclebackConfig: FC = ({ connector, onNameC const [isLoading, setIsLoading] = useState(true); const [copied, setCopied] = useState(false); + // Fetch webhook info // Fetch webhook info useEffect(() => { - const fetchWebhookInfo = async () => { + const controller = new AbortController(); + + const doFetch = async () => { if (!connector.search_space_id) return; const baseUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL; @@ -49,8 +52,11 @@ export const CirclebackConfig: FC = ({ connector, onNameC setIsLoading(true); try { const response = await authenticatedFetch( - `${baseUrl}/api/v1/webhooks/circleback/${connector.search_space_id}/info` + `${baseUrl}/api/v1/webhooks/circleback/${connector.search_space_id}/info`, + { signal: controller.signal } ); + if (controller.signal.aborted) return; + if (response.ok) { const data: unknown = await response.json(); // Runtime validation with zod schema @@ -59,16 +65,18 @@ export const CirclebackConfig: FC = ({ connector, onNameC setWebhookUrl(validatedData.webhook_url); } } catch (error) { + if (controller.signal.aborted) return; console.error("Failed to fetch webhook info:", error); // Reset state on error setWebhookInfo(null); setWebhookUrl(""); } finally { - setIsLoading(false); + if (!controller.signal.aborted) setIsLoading(false); } }; - fetchWebhookInfo(); + doFetch().catch(() => {}); + return () => controller.abort(); }, [connector.search_space_id]); const handleNameChange = (value: string) => { diff --git a/surfsense_web/components/editor-panel/editor-panel.tsx b/surfsense_web/components/editor-panel/editor-panel.tsx index 3ea36f800..3c204f1bb 100644 --- a/surfsense_web/components/editor-panel/editor-panel.tsx +++ b/surfsense_web/components/editor-panel/editor-panel.tsx @@ -70,7 +70,7 @@ export function EditorPanelContent({ const [displayTitle, setDisplayTitle] = useState(title || "Untitled"); useEffect(() => { - let cancelled = false; + const controller = new AbortController(); setIsLoading(true); setError(null); setEditorDoc(null); @@ -78,7 +78,7 @@ export function EditorPanelContent({ initialLoadDone.current = false; changeCountRef.current = 0; - const fetchContent = async () => { + const doFetch = async () => { const token = getBearerToken(); if (!token) { redirectToLogin(); @@ -88,10 +88,10 @@ export function EditorPanelContent({ try { const response = await authenticatedFetch( `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`, - { method: "GET" } + { method: "GET", signal: controller.signal } ); - if (cancelled) return; + if (controller.signal.aborted) return; if (!response.ok) { const errorData = await response @@ -115,18 +115,16 @@ export function EditorPanelContent({ setEditorDoc(data); initialLoadDone.current = true; } catch (err) { - if (cancelled) return; + if (controller.signal.aborted) return; console.error("Error fetching document:", err); setError(err instanceof Error ? err.message : "Failed to fetch document"); } finally { - if (!cancelled) setIsLoading(false); + if (!controller.signal.aborted) setIsLoading(false); } }; - fetchContent(); - return () => { - cancelled = true; - }; + doFetch().catch(() => {}); + return () => controller.abort(); }, [documentId, searchSpaceId, title]); const handleMarkdownChange = useCallback((md: string) => { diff --git a/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx b/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx index ac279cd4d..a645bfbd5 100644 --- a/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx +++ b/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx @@ -55,7 +55,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen const changeCountRef = useRef(0); useEffect(() => { - let cancelled = false; + const controller = new AbortController(); setIsLoading(true); setError(null); setDoc(null); @@ -64,7 +64,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen initialLoadDone.current = false; changeCountRef.current = 0; - const fetchContent = async () => { + const doFetch = async () => { const token = getBearerToken(); if (!token) { redirectToLogin(); @@ -74,10 +74,10 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen try { const response = await authenticatedFetch( `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`, - { method: "GET" } + { method: "GET", signal: controller.signal } ); - if (cancelled) return; + if (controller.signal.aborted) return; if (!response.ok) { const errorData = await response @@ -98,18 +98,16 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen setDoc(data); initialLoadDone.current = true; } catch (err) { - if (cancelled) return; + if (controller.signal.aborted) return; console.error("Error fetching document:", err); setError(err instanceof Error ? err.message : "Failed to fetch document"); } finally { - if (!cancelled) setIsLoading(false); + if (!controller.signal.aborted) setIsLoading(false); } }; - fetchContent(); - return () => { - cancelled = true; - }; + doFetch().catch(() => {}); + return () => controller.abort(); }, [documentId, searchSpaceId]); const handleMarkdownChange = useCallback((md: string) => {