React Query Code Generation#
Overview#
The plugin-react-query package generates TanStack Query hooks and helpers from OpenAPI operations. For every matching HTTP operation it can emit up to four generator variants: standard query, suspense query, infinite query, and suspense-infinite query — each producing a QueryKey type + factory, a queryOptions helper, and optionally a use* hook.
Entry points:
- types.ts —
Options,ResolvedOptions, andResolverReactQuerytypes - queryGenerator.tsx — Standard
useQuerygenerator - suspenseQueryGenerator.tsx — Suspense variant generator
- QueryOptions.tsx —
queryOptionsJSX component internals/tanstack-query/src/components/QueryKey.tsx—QueryKeyJSX component (re-exported viacomponents/QueryKey.tsx)
Generator Variants#
The plugin ships five generators in packages/plugin-react-query/src/generators/:
| Generator | Registered name | Hook emitted | Key TanStack import |
|---|---|---|---|
queryGenerator | react-query | useQuery | useQuery, queryOptions |
suspenseQueryGenerator | react-suspense-query | useSuspenseQuery | useSuspenseQuery, queryOptions |
infiniteQueryGenerator | — | useInfiniteQuery | useInfiniteQuery, infiniteQueryOptions |
suspenseInfiniteQueryGenerator | — | useSuspenseInfiniteQuery | useSuspenseInfiniteQuery |
mutationGenerator | — | useMutation | useMutation |
Each generator's match() function gates which operations it handles. queryGenerator matches operations where isQuery && !isMutation ; suspenseQueryGenerator additionally requires !!suspense && hooks in match() .
Per-File Output Structure#
For each matched operation, a generator resolves four names from the resolver namespace and writes a single file:
fooQueryKey() ← key factory (always emitted)
FooQueryKey ← key type (always emitted)
fooQueryOptions() ← queryOptions helper (always emitted)
useFooQuery() ← hook (only when hooks: true)
The resolver for each variant has its own namespace — resolver.query, resolver.suspenseQuery, resolver.infiniteQuery, resolver.suspenseInfiniteQuery, resolver.mutation — each providing .name(), .optionsName(), .keyName(), .keyTypeName(), .clientName() .
Example resolved names per GET /pet/{petId} :
resolver.query.name(node)→useGetPetByIdresolver.query.optionsName(node)→getPetByIdQueryOptionsresolver.query.keyName(node)→getPetByIdQueryKeyresolver.query.keyTypeName(node)→GetPetByIdQueryKey
QueryKey Component#
QueryKey emits two exports per operation:
- Arrow function (
getPetByIdQueryKey) — returns anas constarray. - Type alias (
GetPetByIdQueryKey) —ReturnType<typeof getPetByIdQueryKey>.
The key array is built by queryKeyTransformer , which inspects the operation for path params, query params, and a request body, then produces elements like { url: '/pet/:petId', params: path }, ...(query ? [query] : []). A custom transformer can be wired in via the queryKey option , overriding the default shape entirely.
QueryOptions Component#
QueryOptions renders a named export function that:
- Calls
queryKeyName(…)to get the key. - Returns
queryOptions<TData, TError, TData, typeof queryKey>({ queryKey, queryFn }). - The
queryFncalls the contract client (await getPetById(…)) and returnsdata.
This helper is always emitted regardless of the hooks setting, making it usable in server components, loaders, or any non-React context.
hooks Option and File Emission Control#
The hooks: boolean option (default false) is the primary switch controlling what gets emitted :
hooks: false— OnlyqueryOptions,mutationOptions,queryKey, andmutationKeyare emitted. The imports come exclusively from@tanstack/react-queryfactory functions that are also available in the Vue, Solid, and Svelte adapters, making the output framework-portable .hooks: true— Adds theuse*hook body to each file. ThequeryGeneratorchecksquery && hooksbefore rendering the<Query>component .
For the suspense and infinite variants, the hooks flag is checked at the outer match() guard level, meaning if hooks is false those generators skip the operation entirely and emit no file at all . This was changed in PR #697 to prevent near-empty queryOptions/queryKey-only files from being written for every GET operation when suspense/infinite was enabled without hooks.
suspense and infinite Options#
Both options default to false and must be explicitly enabled :
suspense: {}— ActivatessuspenseQueryGeneratorandsuspenseInfiniteQueryGenerator. Requires TanStack Query v5+. The suspense generator unconditionally importsuseSuspenseQuery(no conditionalhooksguard in the file body) becausematch()already excludes it whenhooksis false .infinite: Partial<Infinite>— ActivatesinfiniteQueryGenerator. Configure cursor/page parameters via theInfinitetype.
Key Configuration Options#
All in Options (types.ts):
| Option | Type | Default | Purpose |
|---|---|---|---|
hooks | boolean | false | Emit use* hooks; if false only emit framework-agnostic helpers |
suspense | Partial<Suspense> | false | false | Enable suspense query generators |
infinite | Partial<Infinite> | false | false | Enable infinite query generators |
query | Partial<Query> | false | — | Configure query methods; false skips query generation |
mutation | Partial<Mutation> | false | — | Configure mutation methods; false skips mutation generation |
queryKey | Transformer | built-in | Custom key shape transformer |
customOptions | { importPath, name } | — | Wire a user hook that injects extra options into every generated hook |
resolver | ResolverPatch<ResolverReactQuery> | — | Override name/path resolution for any variant |