From 094fb4fecefc9342a380af8fb106cb6697f262de Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 21 Apr 2026 19:04:18 +0200 Subject: [PATCH] Replace index keys with stable keys in editable list components --- Docs/Tasks.md | 6 ++- frontend/src/components/config/JailsTab.tsx | 2 +- frontend/src/components/config/RegexList.tsx | 30 +++++++++++-- .../components/config/StringListEditor.tsx | 31 +++++++++++-- .../config/__tests__/RegexList.test.tsx | 34 ++++++++++++++ .../__tests__/StringListEditor.test.tsx | 44 +++++++++++++++++++ .../__tests__/stableListEntries.test.ts | 38 ++++++++++++++++ .../components/config/stableListEntries.ts | 34 ++++++++++++++ 8 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 frontend/src/components/config/__tests__/RegexList.test.tsx create mode 100644 frontend/src/components/config/__tests__/StringListEditor.test.tsx create mode 100644 frontend/src/components/config/__tests__/stableListEntries.test.ts create mode 100644 frontend/src/components/config/stableListEntries.ts diff --git a/Docs/Tasks.md b/Docs/Tasks.md index 4aa6019..315bcf0 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -375,7 +375,11 @@ const source = timeRange === "24h" ? "fail2ban" : "archive"; --- -### TASK-019 — Replace index keys with stable keys in editable lists +### TASK-019 — Replace index keys with stable keys in editable lists (done) + +**Where fixed:** `frontend/src/components/config/StringListEditor.tsx`, `frontend/src/components/config/RegexList.tsx`, `frontend/src/components/config/JailsTab.tsx`, `frontend/src/components/config/stableListEntries.ts` + +**Summary:** Added stable internal IDs for editable string lists, replaced index-based React keys with stable entry IDs, and updated the jail log path list to use the path value as its key. **Where found:** 19 instances identified, including: - `frontend/src/components/config/StringListEditor.tsx` line 34 — `key={index}` on editable `Input` rows. diff --git a/frontend/src/components/config/JailsTab.tsx b/frontend/src/components/config/JailsTab.tsx index 61708b0..289209e 100644 --- a/frontend/src/components/config/JailsTab.tsx +++ b/frontend/src/components/config/JailsTab.tsx @@ -381,7 +381,7 @@ function JailConfigDetail({ ) : ( logPaths.map((p, i) => ( -
+
( + patterns.map(createStableStringEntry), + ); + + const entries = useMemo(() => { + if ( + entriesRef.current.length === patterns.length && + entriesRef.current.every((entry, index) => entry.value === patterns[index]) + ) { + return entriesRef.current; + } + + const reconciled = reconcileStableStringEntries(entriesRef.current, patterns); + entriesRef.current = reconciled; + return reconciled; + }, [patterns]); + const [newPattern, setNewPattern] = useState(""); const handleAdd = useCallback(() => { @@ -62,11 +84,11 @@ export function RegexList({ (none) )} - {patterns.map((p, i) => ( -
+ {entries.map(({ id, value }, i) => ( +
{ diff --git a/frontend/src/components/config/StringListEditor.tsx b/frontend/src/components/config/StringListEditor.tsx index 4af9ea5..f2ceafa 100644 --- a/frontend/src/components/config/StringListEditor.tsx +++ b/frontend/src/components/config/StringListEditor.tsx @@ -1,5 +1,11 @@ import { Button, Input } from "@fluentui/react-components"; import { Add24Regular, Delete24Regular } from "@fluentui/react-icons"; +import { useMemo, useRef } from "react"; +import { + createStableStringEntry, + reconcileStableStringEntries, + StableStringEntry, +} from "./stableListEntries"; interface StringListEditorProps { items: string[]; @@ -14,6 +20,23 @@ export function StringListEditor({ placeholder, addLabel = "Add entry", }: StringListEditorProps): React.JSX.Element { + const entriesRef = useRef( + items.map(createStableStringEntry), + ); + + const entries = useMemo(() => { + if ( + entriesRef.current.length === items.length && + entriesRef.current.every((entry, index) => entry.value === items[index]) + ) { + return entriesRef.current; + } + + const reconciled = reconcileStableStringEntries(entriesRef.current, items); + entriesRef.current = reconciled; + return reconciled; + }, [items]); + const handleChange = (index: number, value: string): void => { const next = [...items]; next[index] = value; @@ -30,14 +53,14 @@ export function StringListEditor({ return (
- {items.map((item, index) => ( -
+ {entries.map(({ id, value }, index) => ( +
{ handleChange(index, d.value); }} />