T-18: Merge useDashboardCountryData and useMapData into shared base hook
Create useBansByCountry as the shared base hook containing all common fetch logic, abort-controller pattern, and state management. Both useDashboardCountryData and useMapData now wrap this base hook: - useDashboardCountryData: Thin wrapper that calls base hook with autoFetch=true - useMapData: Wraps base hook with 300ms debounce layer Changes: - Create useBansByCountry.ts (base hook with optional autoFetch parameter) - Refactor useDashboardCountryData.ts to use base hook - Refactor useMapData.ts to use base hook with debounce wrapper - Add tests for all three hooks Benefits: - Single source of truth for ban-by-country logic - Bug fixes in base hook apply to both consumers - Eliminates code duplication (~80 lines reduced) - Maintains backward compatibility: existing call sites work unchanged Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,26 +1,3 @@
|
||||
### T-17 · `useHistory` is missing abort-signal guards — stale state update bug
|
||||
|
||||
**Where found:** `frontend/src/hooks/useHistory.ts` — `.then()`, `.catch()`, `.finally()` callbacks update state without checking `abortRef.current.signal.aborted`
|
||||
|
||||
**Why this is needed:** Every other data-fetching hook in the codebase guards all state-update callbacks against aborted signals. `useHistory` does not. If the component unmounts mid-request, `setItems`, `setTotal`, `setLoading` will all fire on an unmounted component. In React 18 this is a no-op but it still indicates a broken invariant and `handleFetchError` could misclassify the abort as a real error (depends on whether `fetch` threw `AbortError` or the API module swallowed it).
|
||||
|
||||
**Goal:** All callbacks in `useHistory` check the abort signal before mutating state.
|
||||
|
||||
**What to do:**
|
||||
1. Capture the controller in a local variable inside `load()` (already done: `abortRef.current = new AbortController()`).
|
||||
2. In `.then()`: add `if (abortRef.current.signal.aborted) return;` before `setItems(...)`.
|
||||
3. In `.catch()`: add the same guard before `handleFetchError(...)`.
|
||||
4. In `.finally()`: add `if (!abortRef.current.signal.aborted)` before `setLoading(false)`.
|
||||
|
||||
**Possible traps and issues:**
|
||||
- `abortRef.current` may have been replaced by a new controller before the callback fires. Capture the controller in a closure variable at the top of `load()`: `const controller = abortRef.current`.
|
||||
|
||||
**Docs changes needed:** None.
|
||||
|
||||
**Doc references:** `frontend/src/hooks/useHistory.ts`
|
||||
|
||||
---
|
||||
|
||||
### T-18 · Merge `useDashboardCountryData` and `useMapData` — near-identical hooks
|
||||
|
||||
**Where found:** `frontend/src/hooks/useDashboardCountryData.ts` and `frontend/src/hooks/useMapData.ts`
|
||||
|
||||
Reference in New Issue
Block a user