Add AbortController cleanup to async frontend effects
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Button, Field, Input, MessageBar, MessageBarBody, Select, Spinner, Text } from "@fluentui/react-components";
|
||||
import { PlayRegular } from "@fluentui/react-icons";
|
||||
import { useCommonSectionStyles } from "../../theme/commonStyles";
|
||||
@@ -25,6 +25,7 @@ export function BlocklistScheduleSection({ onRunImport, runImportRunning }: Sche
|
||||
const { info, loading, error, saveSchedule } = useSchedule();
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saveMsg, setSaveMsg] = useState<string | null>(null);
|
||||
const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const config = info?.config ?? {
|
||||
frequency: "daily" as ScheduleFrequency,
|
||||
@@ -37,12 +38,20 @@ export function BlocklistScheduleSection({ onRunImport, runImportRunning }: Sche
|
||||
const [draft, setDraft] = useState<ScheduleConfig>(config);
|
||||
|
||||
const handleSave = useCallback((): void => {
|
||||
if (saveTimeoutRef.current) {
|
||||
clearTimeout(saveTimeoutRef.current);
|
||||
saveTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
saveSchedule(draft)
|
||||
.then(() => {
|
||||
setSaveMsg("Schedule saved.");
|
||||
setSaving(false);
|
||||
setTimeout(() => { setSaveMsg(null); }, 3000);
|
||||
saveTimeoutRef.current = setTimeout(() => {
|
||||
setSaveMsg(null);
|
||||
saveTimeoutRef.current = null;
|
||||
}, 3000);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
setSaveMsg(err instanceof Error ? err.message : "Failed to save schedule");
|
||||
@@ -50,6 +59,14 @@ export function BlocklistScheduleSection({ onRunImport, runImportRunning }: Sche
|
||||
});
|
||||
}, [draft, saveSchedule]);
|
||||
|
||||
useEffect(() => {
|
||||
return (): void => {
|
||||
if (saveTimeoutRef.current) {
|
||||
clearTimeout(saveTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={sectionStyles.section}>
|
||||
<div className={sectionStyles.sectionHeader}>
|
||||
|
||||
@@ -106,12 +106,14 @@ export function ActivateJailDialog({
|
||||
useEffect(() => {
|
||||
if (!open || !jail) return;
|
||||
|
||||
const controller = new AbortController();
|
||||
setValidating(true);
|
||||
setValidationIssues([]);
|
||||
setValidationWarnings([]);
|
||||
|
||||
onValidate()
|
||||
.then((result) => {
|
||||
if (controller.signal.aborted) return;
|
||||
setValidationIssues(result.issues);
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -119,8 +121,13 @@ export function ActivateJailDialog({
|
||||
// attempt activation and let the server decide.
|
||||
})
|
||||
.finally(() => {
|
||||
if (controller.signal.aborted) return;
|
||||
setValidating(false);
|
||||
});
|
||||
|
||||
return (): void => {
|
||||
controller.abort();
|
||||
};
|
||||
}, [open, jail, onValidate]);
|
||||
|
||||
const handleClose = (): void => {
|
||||
|
||||
@@ -75,25 +75,33 @@ export function ConfFilesTab({
|
||||
const [newContent, setNewContent] = useState("");
|
||||
const [creating, setCreating] = useState(false);
|
||||
|
||||
const loadFiles = useCallback(async () => {
|
||||
const loadFiles = useCallback(async (signal?: AbortSignal) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const resp = await fetchList();
|
||||
if (signal?.aborted) return;
|
||||
setFiles(resp.files);
|
||||
} catch (err: unknown) {
|
||||
if (signal?.aborted) return;
|
||||
setError(
|
||||
err instanceof ApiError
|
||||
? err.message
|
||||
: `Failed to load ${label.toLowerCase()} files.`,
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (!signal?.aborted) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}, [fetchList, label]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadFiles();
|
||||
const controller = new AbortController();
|
||||
void loadFiles(controller.signal);
|
||||
return (): void => {
|
||||
controller.abort();
|
||||
};
|
||||
}, [loadFiles]);
|
||||
|
||||
const handleAccordionToggle = useCallback(
|
||||
|
||||
Reference in New Issue
Block a user