Skip to content

CreateApiClientOptions

function createApiClient<TSchema extends Schema, TParsedError = unknown>(
schema: TSchema,
options: CreateApiClientOptions<TParsedError>,
): ApiClient<TSchema, TParsedError>;
interface CreateApiClientOptions<TParsedError = unknown>
extends CreateFetcherOptions<TParsedError> {
queryClient?: QueryClient;
}

CreateApiClientOptions is CreateFetcherOptions plus one extra field. Everything from Fetcher options applies; the React-specific knob is queryClient.

These behave exactly like in createFetcher. Refer to that page for the full discussion:

FieldTypeRequired
baseUrlstringyes
fetchFntypeof fetchno
defaultHeadersstatic or async-fnno
parseError(res, body) => TParsedErrorno
onError(err, ctx) => voidno
TypeDefault
QueryClient (from @tanstack/react-query)a freshly constructed QueryClient

If you provide a QueryClient, createApiClient uses it as-is. If not, it constructs one with TanStack Query’s defaults.

Use cases:

  • Share defaults across multiple createApiClient instances.
  • Configure defaultOptions (staleTime, gcTime, retry).
  • Hook into persistence, devtools, or other TanStack Query primitives.
  • Embed use-q into an existing TanStack Query setup.
import { QueryClient } from "@tanstack/react-query";
import { createApiClient } from "@use-q/api-client-react";
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 30_000, retry: 1, refetchOnWindowFocus: false },
mutations: { retry: 0 },
},
});
export const api = createApiClient<typeof schema>(schema, {
baseUrl: "https://api.example.com",
queryClient,
});

See BYO QueryClient for full patterns (persistence, devtools, multiple clients).

interface ApiClient<TSchema extends Schema, TParsedError = unknown> {
// Hooks (stable references — safe to destructure at module scope)
useQ: UseQ<TSchema, TParsedError>;
useM: UseM<TSchema, TParsedError>;
useInfiniteQ: UseInfiniteQ<TSchema, TParsedError>;
useSuspenseQ: UseSuspenseQ<TSchema, TParsedError>;
useQClient: () => QClient<TSchema>;
// Plumbing
fetcher: FetcherInstance<TSchema, TParsedError>;
queryClient: QueryClient;
queryKeys: QueryKeyFactory<TSchema>;
isApiError: typeof isApiError;
// Internal (exposed for advanced use)
_tagRegistry: TagRegistry;
}
FieldPurpose
useQRead hook (TanStack Query useQuery semantics).
useMWrite hook with optimistic + tag invalidation.
useInfiniteQPaginated reads; only typed for routes with a pagination block.
useSuspenseQSuspense-friendly read.
useQClientReturns a typed cache-control object.
fetcherThe framework-agnostic fetcher backing all hooks. Use in loaders/RSC.
queryClientThe QueryClient (yours, or constructed). Pass to <QueryClientProvider>.
queryKeysA factory that builds typed query keys per route.
isApiErrorType guard, re-exported for convenience.
_tagRegistryInternal TagRegistry. Exposed for testing and advanced patterns.
createApiClient<TSchema, TParsedError = unknown>(schema, options);
  • TSchema is the schema type. Pass typeof schema for full inference.
  • TParsedError is the parsed-error shape returned by parseError. It flows through every hook’s error.parsed.
interface ApiProblem {
type: string;
title: string;
detail: string;
}
export const api = createApiClient<typeof schema, ApiProblem>(schema, {
baseUrl: "https://api.example.com",
parseError: async (res) => {
const json = (await res.json().catch(() => null)) as ApiProblem | null;
return json ?? { type: "about:blank", title: res.statusText, detail: "" };
},
});

Now useQ/useM/etc. all see error.parsed: ApiProblem.