refactor(frontend): extract shared fetch lifecycle into useFetchData base hook
Eliminates ~100 lines of duplicated code across useListData and usePolledData by creating a composable base hook that handles: - Abort controller lifecycle and cancellation - Loading/error state management - Fetch error handling - Unmount cleanup Changes: - Create hooks/useFetchData.ts with base fetch lifecycle (no effects on consumers) - Refactor useListData to compose useFetchData, returns items array by default - Refactor usePolledData to compose useFetchData, adds polling and focus-refetch - Add comprehensive tests for useFetchData base hook - Document hook architecture and composition pattern in Web-Development.md Result: Both hooks now use shared primitives, reducing maintenance burden and ensuring consistent cancellation/error handling across all data fetches. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -246,6 +246,51 @@ const doBan = useCallback(
|
||||
- ❌ Passing API functions directly to components — couples components to API contract
|
||||
- ❌ Multiple domain hooks for the same data without deduplication — causes wasted requests and state desync
|
||||
|
||||
### Hook Architecture & Reusable Primitives
|
||||
|
||||
BanGUI's hooks are built on composable primitives to eliminate duplication and enforce consistent patterns.
|
||||
|
||||
**Base Hook: `useFetchData`** (`hooks/useFetchData.ts`)
|
||||
- The foundation of all data-fetching hooks
|
||||
- Encapsulates fetch lifecycle: abort controller management, loading/error state, cancellation safety
|
||||
- Signature: `useFetchData(fetcher, selector, errorMessage, onSuccess?, initialData?) → { data, loading, error, refresh }`
|
||||
- Not used directly by consumers; only composed by higher-level hooks
|
||||
- Handles automatic cleanup on unmount (abort signal cancellation)
|
||||
|
||||
**Tier 2 Hooks Built on `useFetchData`:**
|
||||
- `useListData`: Wraps `useFetchData` with `initialData` defaulting to `[]` and returns `{ items, ... }`
|
||||
- `usePolledData`: Wraps `useFetchData` and adds polling (interval) + window-focus refetch on top
|
||||
- Additional specialized hooks can be added by composing `useFetchData` with domain-specific effects
|
||||
|
||||
**Composition Pattern for New Hooks:**
|
||||
When building a new Tier 2 hook with custom behavior, follow this pattern:
|
||||
```ts
|
||||
export function useMyCustomData<TResponse, TData>(options: MyOptions): MyResult {
|
||||
// 1. Use useFetchData for the base fetch lifecycle
|
||||
const { data, loading, error, refresh } = useFetchData({
|
||||
fetcher: options.fetcher,
|
||||
selector: options.selector,
|
||||
errorMessage: options.errorMessage,
|
||||
onSuccess: options.onSuccess,
|
||||
initialData: options.initialData,
|
||||
});
|
||||
|
||||
// 2. Add custom effects for additional behavior (e.g., polling, focus handling, custom cleanup)
|
||||
useEffect(() => {
|
||||
// Your custom logic here
|
||||
}, [...dependencies]);
|
||||
|
||||
// 3. Return a domain-specific result shape
|
||||
return { data, loading, error, refresh, customField: /* derived */ };
|
||||
}
|
||||
```
|
||||
|
||||
**Why this architecture?**
|
||||
- **DRY**: Eliminates duplicate fetch logic across multiple hooks
|
||||
- **Consistency**: All hooks share the same cancellation and error handling semantics
|
||||
- **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
|
||||
|
||||
---
|
||||
|
||||
## 4. Code Organization
|
||||
|
||||
Reference in New Issue
Block a user