feat: Implement global request lifecycle cancellation on route transitions
Adds a navigation-aware request cancellation mechanism that automatically aborts all route-specific API requests when the user navigates to a different route. This prevents silent state-update errors from responses arriving after component unmount and conserves bandwidth by cancelling now-irrelevant requests. Key additions: - NavigationCancellationContext: Context for managing route-specific signals - NavigationCancellationProvider: Provider that detects route changes and aborts all signals from the previous route - useNavigationAbortSignal hook: Allows components to subscribe to navigation-aware cancellation signals - Comprehensive tests for the cancellation lifecycle - Documentation in Web-Development.md for request lifecycle policy The provider is placed in the app hierarchy between BrowserRouter and AuthProvider, ensuring consistent cancellation behavior across all routes. Long-lived background tasks (polling, session validation) can opt-out by managing their own AbortController lifecycle. Closes #23 from Tasks.md: No global cancellation policy on route transitions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -2,13 +2,14 @@
|
||||
* Application root component.
|
||||
*
|
||||
* Provider order (see `src/providers/PROVIDER_ORDER.md` for detailed contract):
|
||||
* 1. `ThemeProvider` — OUTERMOST; provides theme context to AppContents
|
||||
* 2. `FluentProvider` — supplies Fluent UI theme and design tokens
|
||||
* 3. `NotificationProvider` — provides notification service to all descendants
|
||||
* 4. `ErrorBoundary` — catches catastrophic errors
|
||||
* 5. `BrowserRouter` — enables client-side routing via React Router
|
||||
* 6. `AuthProvider` — manages session state; validates on mount; uses useNavigate()
|
||||
* 7. `TimezoneProvider` — INNERMOST (inside protected routes); fetches timezone after auth
|
||||
* 1. `ThemeProvider` — OUTERMOST; provides theme context to AppContents
|
||||
* 2. `FluentProvider` — supplies Fluent UI theme and design tokens
|
||||
* 3. `NotificationProvider` — provides notification service to all descendants
|
||||
* 4. `ErrorBoundary` — catches catastrophic errors
|
||||
* 5. `BrowserRouter` — enables client-side routing via React Router
|
||||
* 6. `NavigationCancellationProvider` — manages route-aware request cancellation
|
||||
* 7. `AuthProvider` — manages session state; validates on mount; uses useNavigate()
|
||||
* 8. `TimezoneProvider` — INNERMOST (inside protected routes); fetches timezone after auth
|
||||
*
|
||||
* CRITICAL: Provider order is order-sensitive. See PROVIDER_ORDER.md before refactoring.
|
||||
*
|
||||
@@ -37,6 +38,7 @@ import { darkTheme, lightTheme } from "./theme/customTheme";
|
||||
import { AuthProvider } from "./providers/AuthProvider";
|
||||
import { ThemeProvider, useThemeMode } from "./providers/ThemeProvider";
|
||||
import { TimezoneProvider } from "./providers/TimezoneProvider";
|
||||
import { NavigationCancellationProvider } from "./providers/NavigationCancellationProvider";
|
||||
import { NotificationProvider } from "./services/notificationService";
|
||||
import { RequireAuth } from "./components/RequireAuth";
|
||||
import { SetupGuard } from "./components/SetupGuard";
|
||||
@@ -60,12 +62,13 @@ const BlocklistsPage = lazy(() => import("./pages/BlocklistsPage").then((m) => (
|
||||
* Root application component — mounts providers and top-level routes.
|
||||
*
|
||||
* Provider stack (see PROVIDER_ORDER.md for detailed contract):
|
||||
* - FluentProvider (2) — receives theme from useThemeMode()
|
||||
* - NotificationProvider (3) — provides notification service
|
||||
* - ErrorBoundary (4) — catches catastrophic errors
|
||||
* - BrowserRouter (5) — enables routing
|
||||
* - AuthProvider (6) — session validation; uses useNavigate()
|
||||
* - TimezoneProvider (7) — inside protected routes only
|
||||
* - FluentProvider (2) — receives theme from useThemeMode()
|
||||
* - NotificationProvider (3) — provides notification service
|
||||
* - ErrorBoundary (4) — catches catastrophic errors
|
||||
* - BrowserRouter (5) — enables routing
|
||||
* - NavigationCancellationProvider (6) — manages route-aware request cancellation
|
||||
* - AuthProvider (7) — session validation; uses useNavigate()
|
||||
* - TimezoneProvider (8) — inside protected routes only
|
||||
*/
|
||||
function AppContents(): React.JSX.Element {
|
||||
const { colorMode } = useThemeMode();
|
||||
@@ -89,9 +92,11 @@ function AppContents(): React.JSX.Element {
|
||||
<NotificationContainer />
|
||||
{/* 5. BrowserRouter — enables routing; required by AuthProvider's useNavigate() */}
|
||||
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
|
||||
<Suspense fallback={<Spinner size="large" label="Loading…" />}>
|
||||
{/* 6. AuthProvider — validates session on mount; must be inside BrowserRouter */}
|
||||
<AuthProvider>
|
||||
{/* 6. NavigationCancellationProvider — manages route-aware request cancellation */}
|
||||
<NavigationCancellationProvider>
|
||||
<Suspense fallback={<Spinner size="large" label="Loading…" />}>
|
||||
{/* 7. AuthProvider — validates session on mount; must be inside BrowserRouter */}
|
||||
<AuthProvider>
|
||||
<Routes>
|
||||
{/* Setup wizard — always accessible; redirects to /login if already done */}
|
||||
<Route
|
||||
@@ -191,6 +196,7 @@ function AppContents(): React.JSX.Element {
|
||||
</Routes>
|
||||
</AuthProvider>
|
||||
</Suspense>
|
||||
</NavigationCancellationProvider>
|
||||
</BrowserRouter>
|
||||
</ErrorBoundary>
|
||||
</NotificationProvider>
|
||||
|
||||
Reference in New Issue
Block a user