# 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` 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 `` 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.