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:
@@ -1,23 +1,3 @@
|
||||
## 22) Magic strings are scattered in frontend storage keys
|
||||
- Where found:
|
||||
- [frontend/src/providers/AuthProvider.tsx](frontend/src/providers/AuthProvider.tsx)
|
||||
- [frontend/src/layouts/MainLayout.tsx](frontend/src/layouts/MainLayout.tsx)
|
||||
- [frontend/src/providers/ThemeProvider.tsx](frontend/src/providers/ThemeProvider.tsx)
|
||||
- Why this is needed:
|
||||
- Repeated literals invite drift and typo regressions.
|
||||
- Goal:
|
||||
- Centralize user/session/local storage keys.
|
||||
- What to do:
|
||||
- Consolidate into a single constants module.
|
||||
- Possible traps and issues:
|
||||
- Existing tests may assume current literal values.
|
||||
- Docs changes needed:
|
||||
- Add storage key registry note.
|
||||
- Doc references:
|
||||
- [frontend/src/utils/constants.ts](frontend/src/utils/constants.ts)
|
||||
|
||||
---
|
||||
|
||||
## 23) No global cancellation policy on route transitions
|
||||
- Where found:
|
||||
- [frontend/src/hooks](frontend/src/hooks)
|
||||
|
||||
@@ -291,6 +291,92 @@ export function useMyCustomData<TResponse, TData>(options: MyOptions): MyResult
|
||||
- **Testability**: Base hook can be tested in isolation; custom effects are minimal and easy to test
|
||||
- **Maintainability**: Bug fixes to abort or error handling only need to happen once
|
||||
|
||||
### Request Lifecycle & Navigation-Aware Cancellation
|
||||
|
||||
BanGUI provides a global cancellation mechanism that automatically aborts route-specific requests when the user navigates away. This prevents:
|
||||
- Silent errors from responses arriving after component unmount
|
||||
- Wasted bandwidth from now-irrelevant requests
|
||||
- State inconsistencies between pages
|
||||
|
||||
**How It Works:**
|
||||
|
||||
The `NavigationCancellationProvider` (wraps the entire router) detects route changes and aborts all `AbortSignal`s obtained from `useNavigationAbortSignal()`. Each route gets its own set of signals that live for the duration of that route.
|
||||
|
||||
**When to Use Navigation Signals:**
|
||||
|
||||
Use `useNavigationAbortSignal()` for data fetches that are **specific to the current route**:
|
||||
- Page-level data fetches (e.g., dashboard stats on the home page)
|
||||
- User-initiated refetches on the current page
|
||||
- Paginated lists, search results, filtered data
|
||||
|
||||
**When NOT to Use:**
|
||||
|
||||
Long-lived background tasks should manage their own lifecycle and NOT use navigation signals:
|
||||
- Session validation (survives route changes)
|
||||
- Service-level polling (e.g., server health checks)
|
||||
- Background syncs that may take longer than a user's stay on a page
|
||||
- Cross-route background operations
|
||||
|
||||
**Usage Pattern:**
|
||||
|
||||
```ts
|
||||
// hooks/useDashboardData.ts
|
||||
export function useDashboardData(): DashboardResult {
|
||||
const signal = useNavigationAbortSignal(); // Gets signal for current route
|
||||
|
||||
const { items: dashStats } = useListData({
|
||||
fetcher: (sig) => fetchDashboardStats(sig || signal), // Use both signals
|
||||
selector: (res) => res.stats,
|
||||
errorMessage: "Failed to load dashboard stats",
|
||||
});
|
||||
|
||||
return { dashStats };
|
||||
}
|
||||
```
|
||||
|
||||
When the user navigates away, the signal is automatically aborted, cancelling any in-flight dashboard stats requests.
|
||||
|
||||
**Opt-Out for Long-Lived Tasks:**
|
||||
|
||||
If a fetch should persist across route changes, do not use `useNavigationAbortSignal()`. Instead, manage your own `AbortController`:
|
||||
|
||||
```ts
|
||||
// Service-level polling — persists across route changes
|
||||
export function useServerHealth(): HealthResult {
|
||||
const [health, setHealth] = useState<Health | null>(null);
|
||||
const controllerRef = useRef<AbortController>(new AbortController());
|
||||
|
||||
useEffect(() => {
|
||||
const poll = async () => {
|
||||
try {
|
||||
const data = await fetchHealth(controllerRef.current.signal);
|
||||
setHealth(data);
|
||||
} catch (err) {
|
||||
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const interval = setInterval(poll, 5000);
|
||||
void poll(); // First fetch immediately
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
controllerRef.current?.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { health };
|
||||
}
|
||||
```
|
||||
|
||||
**Provider Configuration:**
|
||||
|
||||
The `NavigationCancellationProvider` is automatically placed in `App.tsx` inside `BrowserRouter` but before `AuthProvider`. It wraps all routes (including setup and login) to ensure consistent cancellation behavior across the entire app.
|
||||
|
||||
See `src/providers/PROVIDER_ORDER.md` for the full provider hierarchy and dependencies.
|
||||
|
||||
---
|
||||
|
||||
## 4. Code Organization
|
||||
|
||||
Reference in New Issue
Block a user