Fix promise cancellation in 5 components with AbortController refs

Add AbortController refs and abort signal checks to prevent race conditions
and memory leaks when components unmount or new requests are initiated.

Components fixed:
- JailsTab.tsx: validation handler with AbortController pattern
- JailInfoSection.tsx: handle function with useCallback wrapper
- RawConfigSection.tsx: fetch handler with abort checks
- ConfFilesTab.tsx: file fetch handler with abort signal verification
- IgnoreListSection.tsx: three handlers (add, remove, toggle) with callbacks

All handlers now:
1. Abort previous requests before initiating new ones
2. Create and store new AbortController instances
3. Check abort status before state updates in .then()/.catch()
4. Include cleanup effects that abort on unmount

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-01 17:43:47 +02:00
parent c988b4b8b6
commit 96a21ffb70
15 changed files with 291 additions and 73 deletions

View File

@@ -725,19 +725,37 @@ function InactiveJailDetail({
const [validating, setValidating] = useState(false);
const [validationResult, setValidationResult] = useState<JailValidationResult | null>(null);
const [validationError, setValidationError] = useState<string | null>(null);
const validateControllerRef = useRef<AbortController | null>(null);
const handleValidate = useCallback((): void => {
validateControllerRef.current?.abort();
const controller = new AbortController();
validateControllerRef.current = controller;
setValidating(true);
setValidationResult(null);
setValidationError(null);
onValidate()
.then((result) => { setValidationResult(result); })
.then((result) => {
if (controller.signal.aborted) return;
setValidationResult(result);
})
.catch((err: unknown) => {
if (controller.signal.aborted) return;
handleFetchError(err, createStringErrorAdapter(setValidationError), "Validation request failed.");
})
.finally(() => { setValidating(false); });
.finally(() => {
if (controller.signal.aborted) return;
setValidating(false);
});
}, [onValidate]);
useEffect(() => {
return (): void => {
validateControllerRef.current?.abort();
};
}, []);
const blockingIssues: JailValidationIssue[] =
validationResult?.issues.filter((i) => i.field !== "logpath") ?? [];
const advisoryIssues: JailValidationIssue[] =