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

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 }.

  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 queryKeys (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.