Understanding OpenAPI → React Query
Endpoints into hooks, hooks into pages.
What generated hooks look like, the cache-key conventions that decide whether invalidation works, and the mutation pattern that survives a refactor.
One hook per endpoint.
The codegen reads OpenAPI and produces one React-Query hook per operation. AGET /users/{id} becomes useGetUser(id); the hook returns{ data, isLoading, error }. A POST /users becomesuseCreateUser(), returning a mutate function. Each hook is fully typed against the OpenAPI schemas; argument shape and return type are inferred without any handwritten generics.
Cache keys.
React-Query indexes the cache by an array key. The codegen uses a predictable structure: ["getUser", id] for the example above. Two hooks with the same array share the same cache entry; refetching one updates the other. The predictability matters: mutations need to know exactly which cache entries to invalidate, and the answer should not require reading the generator's source.
Mutations that invalidate.
The canonical pattern: useCreateUser on success callsqueryClient.invalidateQueries({ queryKey: ["listUsers"] }). The user list refetches, the new user appears, the UI stays consistent. The codegen can wire this automatically when the spec includes x-related-operationsextensions, or you write the invalidation manually in a thin wrapper. Either way, the cache key must be stable, which is why the predictable structure matters.
A worked page.
A user-profile page calls const { data: user, isLoading } = useGetUser(userId). The generated hook auto-deduplicates if two components on the same page request the same user. While loading, the page shows a skeleton; on success, it renders user.name. The "edit profile" form usesuseUpdateUser; on success it invalidates ["getUser", userId]and the page rerenders with fresh data. Zero handwritten fetch code.
Profile + edit
useGetUser + useUpdateUser
One read hook, one mutation hook, automatic refresh.
invalidateQueries(['getUser', userId]) on edit success
= Consistent UI by default
Suspense vs callback mode.
React-Query supports two modes. Classic: const { data, isLoading } = useQuery(...) — the component handles loading/error states. Suspense: const { data } = useSuspenseQuery(...) — the component renders only when data is ready; loading is handled by a parent <Suspense>. The codegen can emit both; pick one per app. Suspense yields cleaner components but harder error boundaries.
Where it stops helping.
Optimistic updates need code that depends on UI semantics — what to show during the in-flight mutation. WebSocket subscriptions don't fit React-Query's request-response model; use a separate library. Long polls and SSE need custom wrappers arounduseQuery. Authentication flows beyond a static header need an Axios interceptor. The codegen gets you the boring 90 %; the interesting 10 % is still where the product lives.