65 lines
2.8 KiB
Markdown
65 lines
2.8 KiB
Markdown
# Migration path: `useApi` → TanStack Query
|
|
|
|
The current data layer is intentionally dependency-free: a thin `useApi` hook over plain
|
|
service functions, with a mock-adapter flip in `http.js`. This is enough for the foundation,
|
|
but as the number of integrated screens grows we'll want request deduplication, caching,
|
|
background refetch, and mutation state. [TanStack Query](https://tanstack.com/query) is the
|
|
intended destination. This document records how to get there without a rewrite.
|
|
|
|
## What stays (the important part)
|
|
|
|
Nothing in `src/services/*` changes. The service functions (`getDashboard`, `getBookings`,
|
|
`assignMiler`, …) are already framework-agnostic `() => Promise<T>` calls that branch
|
|
mock/real inside `http.js`. TanStack Query wraps these — it does not replace them. The DTO
|
|
typedefs in `services/dto.js` remain the contract.
|
|
|
|
## What changes
|
|
|
|
`hooks/useApi.js` is the only abstraction that maps onto Query. Today:
|
|
|
|
```js
|
|
const { data, loading, error, refetch } = useApi(getDashboard, [], { refreshMs: 30000 });
|
|
```
|
|
|
|
Becomes:
|
|
|
|
```js
|
|
const { data, isLoading: loading, error, refetch } = useQuery({
|
|
queryKey: ['admin', 'dashboard'],
|
|
queryFn: getDashboard,
|
|
refetchInterval: 30000,
|
|
});
|
|
```
|
|
|
|
`AsyncBoundary` keeps working unchanged — it only consumes `{ loading, error, onRetry }`.
|
|
|
|
### Recommended sequencing
|
|
|
|
1. Add deps: `@tanstack/react-query` (+ devtools). Wrap the app in `<QueryClientProvider>` in
|
|
`main.jsx`, just inside `AuthProvider`.
|
|
2. **Shim, don't sweep.** Re-implement `useApi` internally with `useQuery`, deriving a stable
|
|
`queryKey` from the fetcher + deps. Every existing page keeps calling `useApi` and instantly
|
|
gains caching/dedup. This is the lowest-risk step and can ship on its own.
|
|
3. Migrate page-by-page from the `useApi` shim to direct `useQuery` calls with explicit
|
|
`queryKey`s (needed for cache invalidation and cross-screen sharing).
|
|
4. Convert writes (`assignMiler`, `assignVehicle`, `updateBookingStatus`) to `useMutation`
|
|
with `onSuccess: () => queryClient.invalidateQueries(...)`, replacing the current
|
|
OpsStore optimistic-overlay pattern where a real backend now persists the change.
|
|
5. Replace the `realtime.js` polling placeholder with a WebSocket/SSE source that calls
|
|
`queryClient.setQueryData(...)` on each message — Query becomes the single cache the socket
|
|
pushes into.
|
|
|
|
## Query key conventions (when we get there)
|
|
|
|
| Data | Key |
|
|
| --- | --- |
|
|
| Dashboard | `['admin','dashboard']` |
|
|
| Bookings list | `['admin','bookings', filters]` |
|
|
| Booking detail | `['admin','bookings', id]` |
|
|
| Milers | `['admin','milers']` |
|
|
| Consignments | `['admin','consignments']` |
|
|
| Health | `['health']` |
|
|
|
|
Keeping keys hierarchical lets a mutation invalidate `['admin','bookings']` and refresh both
|
|
the list and any open detail view.
|