refactor: eliminate prop drilling in JailsPage with context provider

Replace multi-hop prop forwarding with a dedicated JailContext that manages
jail state and actions. This reduces coupling, simplifies the component hierarchy,
and makes the data flow more explicit.

Changes:
- Create JailContext.tsx with JailProvider and useJailContext hook
- Wrap JailsPage content with JailProvider to expose jail state
- Refactor JailOverviewSection to use useJailContext instead of props
- Remove 10 props from JailOverviewSection component signature
- Add comprehensive documentation on state ownership and prop drilling

Benefits:
- Eliminates unnecessary prop chains through intermediate components
- Makes component contracts clearer (no longer need to pass unrelated props)
- Simplifies future refactoring of jail-related functionality
- Sets a pattern for other page-scoped state management

Testing:
- TypeScript type check passes (tsc --noEmit)
- Frontend builds successfully
- Existing JailsPage tests pass with new context structure

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-28 08:20:29 +02:00
parent ace8930482
commit 69a5f0ceb1
5 changed files with 110 additions and 50 deletions

View File

@@ -3,12 +3,12 @@ import { useJailsPageStyles } from "./jails/jailsPageStyles";
import { JailOverviewSection } from "./jails/JailOverviewSection";
import { BanUnbanForm } from "./jails/BanUnbanForm";
import { IpLookupSection } from "./jails/IpLookupSection";
import { JailProvider, useJailContext } from "./jails/JailContext";
import { useActiveBans } from "../hooks/useActiveBans";
import { useJails } from "../hooks/useJailList";
export function JailsPage(): React.JSX.Element {
function JailsPageContent(): React.JSX.Element {
const styles = useJailsPageStyles();
const { jails, total, loading, error, refresh, startJail, stopJail, setIdle, reloadJail, reloadAll } = useJails();
const { jails } = useJailContext();
const { banIp, unbanIp } = useActiveBans();
const jailNames = jails.map((j) => j.name);
@@ -19,18 +19,7 @@ export function JailsPage(): React.JSX.Element {
Jails
</Text>
<JailOverviewSection
jails={jails}
total={total}
loading={loading}
error={error}
refresh={refresh}
startJail={startJail}
stopJail={stopJail}
setIdle={setIdle}
reloadJail={reloadJail}
reloadAll={reloadAll}
/>
<JailOverviewSection />
<BanUnbanForm jailNames={jailNames} onBan={banIp} onUnban={unbanIp} />
@@ -38,3 +27,11 @@ export function JailsPage(): React.JSX.Element {
</div>
);
}
export function JailsPage(): React.JSX.Element {
return (
<JailProvider>
<JailsPageContent />
</JailProvider>
);
}

View File

@@ -0,0 +1,52 @@
/**
* Context for managing jail state and actions across the jails page.
* Eliminates prop drilling by providing jail data and operations to all descendants.
*/
import { createContext, useContext, useCallback, ReactNode } from "react";
import { useJails } from "../../hooks/useJailList";
import type { JailSummary } from "../../types/jail";
interface JailContextValue {
jails: JailSummary[];
total: number;
loading: boolean;
error: string | null;
refresh: () => void;
startJail: (name: string) => Promise<void>;
stopJail: (name: string) => Promise<void>;
setIdle: (name: string, on: boolean) => Promise<void>;
reloadJail: (name: string) => Promise<void>;
reloadAll: () => Promise<void>;
}
const JailContext = createContext<JailContextValue | undefined>(undefined);
interface JailProviderProps {
children: ReactNode;
}
/**
* Provider component for jail state. Wrap JailsPage and its children with this.
*/
export function JailProvider({ children }: JailProviderProps): React.JSX.Element {
const jailState = useJails();
const value: JailContextValue = useCallback(() => ({
...jailState,
}), [jailState])() as JailContextValue;
return <JailContext.Provider value={value}>{children}</JailContext.Provider>;
}
/**
* Hook to access jail state and actions.
* Must be used within a JailProvider.
*/
export function useJailContext(): JailContextValue {
const context = useContext(JailContext);
if (!context) {
throw new Error("useJailContext must be used within a JailProvider");
}
return context;
}

View File

@@ -27,6 +27,7 @@ import {
} from "@fluentui/react-icons";
import { useCommonSectionStyles } from "../../components/commonStyles";
import { useJailsPageStyles } from "./jailsPageStyles";
import { useJailContext } from "./JailContext";
import type { JailSummary } from "../../types/jail";
const useOverviewStyles = makeStyles({
@@ -47,21 +48,8 @@ const useOverviewStyles = makeStyles({
},
});
interface JailOverviewSectionProps {
jails: JailSummary[];
total: number;
loading: boolean;
error: string | null;
refresh: () => void;
startJail: (name: string) => Promise<void>;
stopJail: (name: string) => Promise<void>;
setIdle: (name: string, on: boolean) => Promise<void>;
reloadJail: (name: string) => Promise<void>;
reloadAll: () => Promise<void>;
}
export function JailOverviewSection(props: JailOverviewSectionProps): React.JSX.Element {
const { jails, total, loading, error, refresh, startJail, stopJail, setIdle, reloadJail, reloadAll } = props;
export function JailOverviewSection(): React.JSX.Element {
const { jails, total, loading, error, refresh, startJail, stopJail, setIdle, reloadJail, reloadAll } = useJailContext();
const pageStyles = useJailsPageStyles();
const overviewStyles = useOverviewStyles();
const sectionStyles = useCommonSectionStyles();