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:
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
@@ -35,25 +35,54 @@ export function IgnoreListSection({
|
||||
const sectionStyles = useCommonSectionStyles();
|
||||
const [inputVal, setInputVal] = useState("");
|
||||
const [opError, setOpError] = useState<string | null>(null);
|
||||
const opControllerRef = useRef<AbortController | null>(null);
|
||||
|
||||
const handleAdd = (): void => {
|
||||
const handleAdd = useCallback((): void => {
|
||||
if (!inputVal.trim()) return;
|
||||
opControllerRef.current?.abort();
|
||||
const controller = new AbortController();
|
||||
opControllerRef.current = controller;
|
||||
|
||||
setOpError(null);
|
||||
onAdd(inputVal.trim())
|
||||
.then(() => {
|
||||
if (controller.signal.aborted) return;
|
||||
setInputVal("");
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (controller.signal.aborted) return;
|
||||
setOpError(err instanceof Error ? err.message : String(err));
|
||||
});
|
||||
};
|
||||
}, [inputVal, onAdd]);
|
||||
|
||||
const handleRemove = useCallback((ip: string): void => {
|
||||
opControllerRef.current?.abort();
|
||||
const controller = new AbortController();
|
||||
opControllerRef.current = controller;
|
||||
|
||||
const handleRemove = (ip: string): void => {
|
||||
setOpError(null);
|
||||
onRemove(ip).catch((err: unknown) => {
|
||||
if (controller.signal.aborted) return;
|
||||
setOpError(err instanceof Error ? err.message : String(err));
|
||||
});
|
||||
};
|
||||
}, [onRemove]);
|
||||
|
||||
const handleToggleIgnoreSelf = useCallback((checked: boolean): void => {
|
||||
opControllerRef.current?.abort();
|
||||
const controller = new AbortController();
|
||||
opControllerRef.current = controller;
|
||||
|
||||
onToggleIgnoreSelf(checked).catch((err: unknown) => {
|
||||
if (controller.signal.aborted) return;
|
||||
setOpError(err instanceof Error ? err.message : String(err));
|
||||
});
|
||||
}, [onToggleIgnoreSelf]);
|
||||
|
||||
useEffect(() => {
|
||||
return (): void => {
|
||||
opControllerRef.current?.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={sectionStyles.section}>
|
||||
@@ -70,9 +99,7 @@ export function IgnoreListSection({
|
||||
label="Ignore self — exclude this server's own IP addresses from banning"
|
||||
checked={ignoreSelf}
|
||||
onChange={(_e, data): void => {
|
||||
onToggleIgnoreSelf(data.checked).catch((err: unknown) => {
|
||||
setOpError(err instanceof Error ? err.message : String(err));
|
||||
});
|
||||
handleToggleIgnoreSelf(data.checked);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Badge,
|
||||
@@ -32,24 +32,40 @@ export function JailInfoSection({ jail, onRefresh, onStart, onStop, onSetIdle, o
|
||||
const sectionStyles = useCommonSectionStyles();
|
||||
const navigate = useNavigate();
|
||||
const [ctrlError, setCtrlError] = useState<string | null>(null);
|
||||
const controllerRef = useRef<AbortController | null>(null);
|
||||
|
||||
const handle =
|
||||
(fn: () => Promise<unknown>, postNavigate = false) =>
|
||||
(): void => {
|
||||
setCtrlError(null);
|
||||
fn()
|
||||
.then(() => {
|
||||
if (postNavigate) {
|
||||
navigate("/jails");
|
||||
} else {
|
||||
onRefresh();
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
setCtrlError(msg);
|
||||
});
|
||||
const handle = useCallback(
|
||||
(fn: () => Promise<unknown>, postNavigate = false): (() => void) => {
|
||||
return (): void => {
|
||||
controllerRef.current?.abort();
|
||||
const controller = new AbortController();
|
||||
controllerRef.current = controller;
|
||||
|
||||
setCtrlError(null);
|
||||
fn()
|
||||
.then(() => {
|
||||
if (controller.signal.aborted) return;
|
||||
if (postNavigate) {
|
||||
navigate("/jails");
|
||||
} else {
|
||||
onRefresh();
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (controller.signal.aborted) return;
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
setCtrlError(msg);
|
||||
});
|
||||
};
|
||||
},
|
||||
[navigate, onRefresh]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return (): void => {
|
||||
controllerRef.current?.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={sectionStyles.section}>
|
||||
|
||||
Reference in New Issue
Block a user