use-q

Command Palette

Search for a command to run...

Introduction

use-q is an opinionated, schema-driven way to use TanStack Query v5 — TkDodo's Practical React Query patterns as a typed API client.

use-q is an opinionated way to use TanStack Query v5. The opinions are Dominik Dorfmeister (TkDodo)'s, from the Practical React Query series — encoded as a thin, schema-driven API client.

You describe your API once as a plain object of RouteDefinitions. From that single source of truth, use-q infers path params, search params, request bodies, and response shapes, builds hierarchical query keys, and wires up cache invalidation automatically through schema-defined tags. No any leaks into your components, and no query keys are written by hand.

You still use TanStack Query. use-q does not replace it or hide the QueryClient — it makes the recommended patterns the path of least resistance. See Practical React Query for how each API maps to TkDodo's posts.

The packages

PackageWhat it provides
@use-q/api-clientFramework-agnostic core: createFetcher, the Schema / RouteDefinition types, and error helpers. Zero runtime dependencies.
@use-q/api-client-reactcreateApiClient and the React hooks (useQ, useM, useInfiniteQ, useSuspenseQ, useQClient) on top of TanStack Query v5.
@use-q/api-client-codegenGenerates a typed schema from an OpenAPI 3.x spec. Ships the use-q-codegen CLI bin.

The core runs anywhere fetch does — Node, Bun, Deno, edge workers, or the browser — so you can share one schema between server actions, loaders, scripts, and your React app.

A quick taste

Define a schema and create a client:

import { createApiClient } from "@use-q/api-client-react";
import type { RouteDefinition } from "@use-q/api-client";
 
interface Post {
  id: string;
  title: string;
}
 
const schema = {
  listPosts: {
    method: "GET",
    path: "/facilities/{facilityId}/posts",
    tags: ["posts"],
  } satisfies RouteDefinition<{ facilityId: string }, never, never, Post[]>,
  createPost: {
    method: "POST",
    path: "/facilities/{facilityId}/posts",
    invalidatesTags: ["posts"],
  } satisfies RouteDefinition<{ facilityId: string }, never, { title: string }, Post>,
} as const;
 
export const api = createApiClient(schema, {
  baseUrl: "https://api.example.com",
});
 
export const { useQ, useM, useQClient } = api;

Then use it in a component:

function PostList({ facilityId }: { facilityId: string }) {
  const { data, isLoading } = api.useQ("listPosts", { params: { facilityId } });
  const createPost = api.useM("createPost");
 
  if (isLoading) return <p>Loading…</p>;
  return (
    <>
      {data?.map((p) => <article key={p.id}>{p.title}</article>)}
      <button
        onClick={() =>
          createPost.mutate({ params: { facilityId }, body: { title: "New post" } })
        }
      >
        Add
      </button>
    </>
  );
}

That's it — the list refetches automatically after the mutation, because createPost.invalidatesTags matches listPosts.tags.

Design principles

These are TkDodo's Practical React Query defaults, as a library:

  • End-to-end type safety. Path params, search params, request bodies, and response shapes are all inferred from a single RouteDefinition map — not from useQuery generics.
  • A query abstraction, not a new mental model. createApiClient is the colocated queryKey + queryFn layer. You still mount QueryClientProvider and pass staleTime / select / enabled.
  • Hierarchical query keys you never type twice. Keys are always ["api", method, path, searchParams]. Prefix invalidation and a queryKeys factory come for free.
  • Schema-driven invalidation. Tag routes with tags / invalidatesTags and mutations refresh the right queries in onSettled through a shared TagRegistry.
  • Optimistic updates that compose. Update multiple caches per mutation with snapshot/rollback semantics built in. Combine with additionalInvalidatesTags for arbitrary fan-out.
  • Framework-agnostic core. @use-q/api-client is a tiny zero-dependency fetcher that runs in Node, Bun, Deno, edge workers, or the browser. Use it from server actions, loaders, or scripts — no React required.
  • OpenAPI codegen. Generate a typed schema directly from an OpenAPI 3.x spec with use-q-codegen from @use-q/api-client-codegen. Pagination is detected automatically.
  • Bring your own QueryClient. Reuse a single QueryClient across multiple createApiClient instances, or hook into persistence and devtools — use-q never gets in the way.

Where to next?

LLM-readable docs

Agents can read these docs at /llms.txt (an index of every page) or /llms-full.txt (the complete corpus in one file). Each page is also available as Markdown by appending .md to its URL.