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:
53
frontend/src/hooks/useNavigationAbortSignal.ts
Normal file
53
frontend/src/hooks/useNavigationAbortSignal.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Hook to subscribe to navigation-aware request cancellation.
|
||||
*
|
||||
* Returns an AbortSignal that is automatically aborted when the user
|
||||
* navigates to a different route. Use this signal to cancel API requests
|
||||
* that are specific to the current route and should not survive a navigation.
|
||||
*
|
||||
* Usage:
|
||||
* const signal = useNavigationAbortSignal();
|
||||
* const { items } = useListData({
|
||||
* fetcher: (sig) => fetchBans(sig || signal),
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
* When to use:
|
||||
* - For page-level data fetches that should not persist across navigation
|
||||
* - For user-initiated refetches on the current page
|
||||
* - For paginated lists, search results, filters
|
||||
*
|
||||
* When NOT to use:
|
||||
* - For long-lived background polls (use your own AbortController instead)
|
||||
* - For service-level state syncs (e.g., session validation)
|
||||
* - For actions that may take longer than a user interaction timeout
|
||||
*
|
||||
* Note: The signal may already be aborted at the time you check it,
|
||||
* depending on timing. This is safe — fetchers should handle aborted
|
||||
* signals gracefully by throwing/catching AbortError.
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
import { NavigationCancellationContext } from "../providers/NavigationCancellationContext";
|
||||
|
||||
/**
|
||||
* Get an AbortSignal for the current route's request lifecycle.
|
||||
*
|
||||
* The returned signal will be aborted when the user navigates away.
|
||||
* All requests using this signal will be automatically cancelled.
|
||||
*
|
||||
* @returns AbortSignal tied to the current route
|
||||
* @throws Error if called outside NavigationCancellationProvider
|
||||
*/
|
||||
export function useNavigationAbortSignal(): AbortSignal {
|
||||
const context = useContext(NavigationCancellationContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"useNavigationAbortSignal must be used within NavigationCancellationProvider. " +
|
||||
"Wrap your router with <NavigationCancellationProvider> in App.tsx.",
|
||||
);
|
||||
}
|
||||
|
||||
return context.getNavigationSignal();
|
||||
}
|
||||
Reference in New Issue
Block a user