2.8 KiB
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 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:
const { data, loading, error, refetch } = useApi(getDashboard, [], { refreshMs: 30000 });
Becomes:
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
- Add deps:
@tanstack/react-query(+ devtools). Wrap the app in<QueryClientProvider>inmain.jsx, just insideAuthProvider. - Shim, don't sweep. Re-implement
useApiinternally withuseQuery, deriving a stablequeryKeyfrom the fetcher + deps. Every existing page keeps callinguseApiand instantly gains caching/dedup. This is the lowest-risk step and can ship on its own. - Migrate page-by-page from the
useApishim to directuseQuerycalls with explicitqueryKeys (needed for cache invalidation and cross-screen sharing). - Convert writes (
assignMiler,assignVehicle,updateBookingStatus) touseMutationwithonSuccess: () => queryClient.invalidateQueries(...), replacing the current OpsStore optimistic-overlay pattern where a real backend now persists the change. - Replace the
realtime.jspolling placeholder with a WebSocket/SSE source that callsqueryClient.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.