diff --git a/Docs/Tasks.md b/Docs/Tasks.md index 25ef749..db97933 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -1,15 +1,3 @@ - -### ✅ TASK-BUG-01 — Infinite Re-Fetch Loop in `useJailConfigs` — DONE - -**Fix Summary** -Wrapped the `onSuccess` callback in `useCallback` with empty dependencies in `frontend/src/hooks/useJailConfigs.ts` (lines 33-35). The inline callback was creating a new reference on every render, which caused `useListData`'s internal `refresh` function to be recreated (since `onSuccess` is in its deps), which triggered the `useEffect` again, causing an infinite fetch loop. - -Added comprehensive test coverage in `frontend/src/hooks/__tests__/useJailConfigs.test.ts` to verify the hook no longer triggers infinite refetches. Updated `Docs/Refactoring.md` with documentation explaining the `onSuccess` stability requirement for all `useListData` callers. - -Commit: `de8af09a3da36dbf24b56fa28656673b232b5e91` - ---- - ### TASK-BUG-02 — `ConfigPage` Tab Switch Destroys All Form State **Where found** @@ -213,7 +201,6 @@ None required. **Why this is needed** Typing `"500"` in the Lines field currently fires three HTTP requests (`"5"`, `"50"`, `"500"`). Each request fetches potentially hundreds of log lines and serializes them, adding unnecessary backend load. - --- ### TASK-ABORT-01 — Missing `signal` Parameter on Multiple API Functions @@ -640,4 +627,4 @@ Remove the `console.log` call. None required. **Why this is needed** -Debug logs in test files pollute the test runner output and make it harder to spot real failures or warnings. +Debug logs in test files pollute the test runner output and make it harder to spot real failures or warnings. \ No newline at end of file diff --git a/Docs/Web-Development.md b/Docs/Web-Development.md index 806fe85..063a826 100644 --- a/Docs/Web-Development.md +++ b/Docs/Web-Development.md @@ -270,6 +270,12 @@ function BanCard({ isHighlighted }: BanCardProps): JSX.Element { - Supply a `key` prop whenever rendering lists — never use array indices as keys if the list can reorder. - Prefer Fluent UI components (`Button`, `Table`, `Input`, …) over raw HTML elements for any interactive or styled element. +### Tab Panels + +- **Never** use `key` on a tab panel wrapper to switch between tabs. This causes the entire subtree to unmount and remount, destroying all state, pending saves, and form input. +- Instead, render all tab panels and use CSS `display: none` / `display: block` to hide inactive tabs, keeping components mounted across tab switches. +- All tab components remain mounted throughout the page lifetime. Hooks continue to run in hidden tabs — if a tab-specific effect must only run on activation, use an explicit activation flag rather than relying on mount/unmount. + ```tsx import { Table, TableBody, TableRow, TableCell, Button } from "@fluentui/react-components"; import type { Ban } from "../types/ban"; diff --git a/frontend/src/pages/ConfigPage.tsx b/frontend/src/pages/ConfigPage.tsx index b250c2c..a448f2a 100644 --- a/frontend/src/pages/ConfigPage.tsx +++ b/frontend/src/pages/ConfigPage.tsx @@ -36,7 +36,11 @@ const useStyles = makeStyles({ header: { marginBottom: tokens.spacingVerticalL, }, - tabContent: { + tabPanel: { + display: "none", + }, + tabPanelVisible: { + display: "block", marginTop: tokens.spacingVerticalL, animationName: "fadeInUp", animationDuration: tokens.durationNormal, @@ -94,16 +98,26 @@ export function ConfigPage(): React.JSX.Element { Regex Tester -
- {tab === "jails" && ( - - )} - {tab === "filters" && } - {tab === "actions" && } - {tab === "server" && } - {tab === "regex" && } +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
); diff --git a/frontend/src/pages/__tests__/ConfigPage.test.tsx b/frontend/src/pages/__tests__/ConfigPage.test.tsx index 7f7173c..8e908aa 100644 --- a/frontend/src/pages/__tests__/ConfigPage.test.tsx +++ b/frontend/src/pages/__tests__/ConfigPage.test.tsx @@ -38,7 +38,8 @@ describe("ConfigPage", () => { renderPage(); fireEvent.click(screen.getByRole("tab", { name: /filters/i })); expect(screen.getByTestId("filters-tab")).toBeInTheDocument(); - expect(screen.queryByTestId("jails-tab")).not.toBeInTheDocument(); + // Jails tab remains mounted (not removed from DOM), just hidden with CSS + expect(screen.getByTestId("jails-tab")).toBeInTheDocument(); }); it("switches to Actions tab when Actions tab is clicked", () => {