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

@@ -5,7 +5,7 @@
* ``POST /api/config/actions`` on confirmation.
*/
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import {
Button,
Dialog,
@@ -73,6 +73,7 @@ export function CreateActionDialog({
const [actionunban, setActionunban] = useState("");
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const submitControllerRef = useRef<AbortController | null>(null);
// Reset form when the dialog opens.
useEffect(() => {
@@ -99,23 +100,36 @@ export function CreateActionDialog({
actionunban: actionunban.trim() || null,
};
submitControllerRef.current?.abort();
const controller = new AbortController();
submitControllerRef.current = controller;
setSubmitting(true);
setError(null);
onCreateAction(req)
.then((action) => {
if (controller.signal.aborted) return;
onCreate(action);
})
.catch((err: unknown) => {
if (controller.signal.aborted) return;
setError(
err instanceof ApiError ? err.message : "Failed to create action.",
);
})
.finally(() => {
if (controller.signal.aborted) return;
setSubmitting(false);
});
}, [name, actionban, actionunban, submitting, onCreate, onCreateAction]);
useEffect(() => {
return (): void => {
submitControllerRef.current?.abort();
};
}, []);
const canConfirm = name.trim() !== "" && !submitting;
return (