OrvalOrval

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:

orval.config.ts
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:

orval.config.ts
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:

orval.config.ts
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:

  • enabled does not gate refetch(), so a retry button sends the request with the unresolved param in the URL. With skipToken no request is sent — the query rejects with Missing queryFn instead, which is the tradeoff to know about before enabling the option.
  • enabled is a single option and ...queryOptions is spread last, so useShowPetById(petId, { query: { enabled: isTabVisible } }) replaces the generated param check. With skipToken the 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.

On this page