React Query
Generate type-safe React Query hooks from OpenAPI
Generate fully typed TanStack Query hooks from your OpenAPI specification.
Configuration
Set the client option to react-query:
import { defineConfig } from 'orval';
export default defineConfig({
petstore: {
output: {
mode: 'tags-split',
target: 'src/api/petstore.ts',
schemas: 'src/api/model',
client: 'react-query',
mock: true,
},
input: {
target: './petstore.yaml',
},
},
});Generated Output
Orval generates one custom hook per path in your OpenAPI specification. For example, this specification generates:
export const showPetById = (
petId: string,
options?: AxiosRequestConfig,
): Promise<AxiosResponse<Pet>> => {
return axios.get(`/pets/${petId}`, options);
};
export const getShowPetByIdQueryKey = (petId: string) => [`/pets/${petId}`];
export const useShowPetById = <
TData = AsyncReturnType<typeof showPetById>,
TError = Error,
>(
petId: string,
options?: {
query?: UseQueryOptions<AsyncReturnType<typeof showPetById>, TError, TData>;
axios?: AxiosRequestConfig;
},
) => {
const { query: queryOptions, axios: axiosOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getShowPetByIdQueryKey(petId);
const queryFn = () => showPetById(petId, axiosOptions);
const query = useQuery<AsyncReturnType<typeof queryFn>, TError, TData>(
queryKey,
queryFn,
{
enabled: petId !== null && petId !== undefined,
...queryOptions,
},
);
return {
queryKey,
...query,
};
};Infinite Queries
Generate useInfiniteQuery hooks alongside regular queries:
import { defineConfig } from 'orval';
export default defineConfig({
petstore: {
output: {
client: 'react-query',
override: {
query: {
useQuery: true,
useInfinite: true,
useInfiniteQueryParam: 'nextId',
options: {
staleTime: 10000,
},
},
},
},
input: {
target: './petstore.yaml',
},
},
});Per-Operation Overrides
Override query options for specific operations:
import { defineConfig } from 'orval';
export default defineConfig({
petstore: {
output: {
client: 'react-query',
override: {
operations: {
listPets: {
query: {
useInfinite: true,
useInfiniteQueryParam: 'cursor',
},
},
},
},
},
input: {
target: './petstore.yaml',
},
},
});Set Query Data
When useSetQueryData: true is set, Orval generates type-safe helper functions to update cached query data:
export const useSetListPetsQueryData = () => {
const queryClient = useQueryClient();
return (
params: ListPetsParams | undefined,
updater:
| Awaited<ReturnType<typeof listPets>>
| undefined
| ((
old: Awaited<ReturnType<typeof listPets>> | undefined,
) => Awaited<ReturnType<typeof listPets>> | undefined),
) => {
queryClient.setQueriesData<Awaited<ReturnType<typeof listPets>>>(
{ queryKey: getListPetsQueryKey(params) },
updater,
);
};
};The helper uses setQueriesData so query keys are matched by prefix. Pass undefined for query params or body to update every cached entry that shares the same path — useful when a mutation should patch a record across all filter/pagination variants:
const updatePetList = useSetListPetsQueryData();
updatePetList(undefined, (old) =>
// invoked once per matched cache entry — return the new shape for that entry
old?.map((pet) => (pet.id === updated.id ? updated : pet)),
);Prior to this change the helper called setQueryData, which writes (and creates) a single exact-key entry. setQueriesData only updates entries that already exist and matches by prefix, so calls with a fully-specified key may behave differently — review existing call sites when upgrading.
Get Query Data
When useGetQueryData: true is set, Orval generates type-safe helper functions to read cached query data:
export const useGetListPetsQueryData = () => {
const queryClient = useQueryClient();
return (params: ListPetsParams) =>
queryClient.getQueryData<Awaited<ReturnType<typeof listPets>>>(
getListPetsQueryKey(params),
);
};Skip Token
When useSkipToken: true is set, a query whose params are not resolved yet is held with skipToken rather than the generated enabled guard:
export const getShowPetByIdQueryOptions = <...>(
petId: string,
options?: {...},
) => {
const queryKey = queryOptions?.queryKey ?? getShowPetByIdQueryKey(petId);
const queryFn: QueryFunction<...> = ({ signal }) => showPetById(petId, signal);
return {
queryKey,
queryFn: petId === null || petId === undefined ? skipToken : queryFn,
...queryOptions,
} as ...;
};This matters in two cases the enabled guard does not cover:
enableddoes not gaterefetch(), so a retry button sends the request with the unresolved param in the URL. WithskipTokenno request is sent — the query rejects withMissing queryFninstead, which is the tradeoff to know about before enabling the option.enabledis a single option and...queryOptionsis spread last, souseShowPetById(petId, { query: { enabled: isTabVisible } })replaces the generated param check. WithskipTokenthe two gates are independent.
Pair it with allParamsOptional so callers can pass an unresolved param without a cast.
Suspense queries are unaffected: TanStack excludes SkipToken from their queryFn, which is also why they get no enabled guard.
Full Example
See the complete React Query example on GitHub.