fix(hooks): add AbortController to properly cancel fetch requests on unmount

This commit is contained in:
SohamBhattacharjee2003 2026-04-03 14:29:41 +05:30
parent 0cd2b8164d
commit 8a8e5fcd76
4 changed files with 34 additions and 24 deletions

6
package-lock.json generated Normal file
View file

@ -0,0 +1,6 @@
{
"name": "SurfSense",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View file

@ -34,9 +34,12 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({ connector, onNameC
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
// Fetch webhook info
// Fetch webhook info // Fetch webhook info
useEffect(() => { useEffect(() => {
const fetchWebhookInfo = async () => { const controller = new AbortController();
const doFetch = async () => {
if (!connector.search_space_id) return; if (!connector.search_space_id) return;
const baseUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL; const baseUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL;
@ -49,8 +52,11 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({ connector, onNameC
setIsLoading(true); setIsLoading(true);
try { try {
const response = await authenticatedFetch( 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) { if (response.ok) {
const data: unknown = await response.json(); const data: unknown = await response.json();
// Runtime validation with zod schema // Runtime validation with zod schema
@ -59,16 +65,18 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({ connector, onNameC
setWebhookUrl(validatedData.webhook_url); setWebhookUrl(validatedData.webhook_url);
} }
} catch (error) { } catch (error) {
if (controller.signal.aborted) return;
console.error("Failed to fetch webhook info:", error); console.error("Failed to fetch webhook info:", error);
// Reset state on error // Reset state on error
setWebhookInfo(null); setWebhookInfo(null);
setWebhookUrl(""); setWebhookUrl("");
} finally { } finally {
setIsLoading(false); if (!controller.signal.aborted) setIsLoading(false);
} }
}; };
fetchWebhookInfo(); doFetch().catch(() => {});
return () => controller.abort();
}, [connector.search_space_id]); }, [connector.search_space_id]);
const handleNameChange = (value: string) => { const handleNameChange = (value: string) => {

View file

@ -70,7 +70,7 @@ export function EditorPanelContent({
const [displayTitle, setDisplayTitle] = useState(title || "Untitled"); const [displayTitle, setDisplayTitle] = useState(title || "Untitled");
useEffect(() => { useEffect(() => {
let cancelled = false; const controller = new AbortController();
setIsLoading(true); setIsLoading(true);
setError(null); setError(null);
setEditorDoc(null); setEditorDoc(null);
@ -78,7 +78,7 @@ export function EditorPanelContent({
initialLoadDone.current = false; initialLoadDone.current = false;
changeCountRef.current = 0; changeCountRef.current = 0;
const fetchContent = async () => { const doFetch = async () => {
const token = getBearerToken(); const token = getBearerToken();
if (!token) { if (!token) {
redirectToLogin(); redirectToLogin();
@ -88,10 +88,10 @@ export function EditorPanelContent({
try { try {
const response = await authenticatedFetch( const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`, `${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) { if (!response.ok) {
const errorData = await response const errorData = await response
@ -115,18 +115,16 @@ export function EditorPanelContent({
setEditorDoc(data); setEditorDoc(data);
initialLoadDone.current = true; initialLoadDone.current = true;
} catch (err) { } catch (err) {
if (cancelled) return; if (controller.signal.aborted) return;
console.error("Error fetching document:", err); console.error("Error fetching document:", err);
setError(err instanceof Error ? err.message : "Failed to fetch document"); setError(err instanceof Error ? err.message : "Failed to fetch document");
} finally { } finally {
if (!cancelled) setIsLoading(false); if (!controller.signal.aborted) setIsLoading(false);
} }
}; };
fetchContent(); doFetch().catch(() => {});
return () => { return () => controller.abort();
cancelled = true;
};
}, [documentId, searchSpaceId, title]); }, [documentId, searchSpaceId, title]);
const handleMarkdownChange = useCallback((md: string) => { const handleMarkdownChange = useCallback((md: string) => {

View file

@ -55,7 +55,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
const changeCountRef = useRef(0); const changeCountRef = useRef(0);
useEffect(() => { useEffect(() => {
let cancelled = false; const controller = new AbortController();
setIsLoading(true); setIsLoading(true);
setError(null); setError(null);
setDoc(null); setDoc(null);
@ -64,7 +64,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
initialLoadDone.current = false; initialLoadDone.current = false;
changeCountRef.current = 0; changeCountRef.current = 0;
const fetchContent = async () => { const doFetch = async () => {
const token = getBearerToken(); const token = getBearerToken();
if (!token) { if (!token) {
redirectToLogin(); redirectToLogin();
@ -74,10 +74,10 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
try { try {
const response = await authenticatedFetch( const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`, `${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) { if (!response.ok) {
const errorData = await response const errorData = await response
@ -98,18 +98,16 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
setDoc(data); setDoc(data);
initialLoadDone.current = true; initialLoadDone.current = true;
} catch (err) { } catch (err) {
if (cancelled) return; if (controller.signal.aborted) return;
console.error("Error fetching document:", err); console.error("Error fetching document:", err);
setError(err instanceof Error ? err.message : "Failed to fetch document"); setError(err instanceof Error ? err.message : "Failed to fetch document");
} finally { } finally {
if (!cancelled) setIsLoading(false); if (!controller.signal.aborted) setIsLoading(false);
} }
}; };
fetchContent(); doFetch().catch(() => {});
return () => { return () => controller.abort();
cancelled = true;
};
}, [documentId, searchSpaceId]); }, [documentId, searchSpaceId]);
const handleMarkdownChange = useCallback((md: string) => { const handleMarkdownChange = useCallback((md: string) => {