Files
doormile_app_web/docs/migration-tanstack-query.md
2026-06-11 21:28:04 +05:30

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.