www/docs/client/tanstack-react-query/setup.mdx
Compared to our classic React Query Integration this client is simpler and more TanStack Query-native, providing factories for common TanStack React Query interfaces like QueryKeys, QueryOptions, and MutationOptions. We think it's the future and recommend using this over the classic client, <a href="/blog/introducing-tanstack-react-query-client">read the announcement post</a> for more information about this change.
:::tip You can try this integration out on the homepage of tRPC.io: https://trpc.io/?try=minimal-react#try-it-out :::
<details> <summary>❓ Do I have to use an integration?</summary>No! The integration is fully optional. You can use @tanstack/react-query using just a vanilla tRPC client, although then you'll have to manually manage query keys and do not get the same level of DX as when using the integration package.
// @filename: server/router.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
const appRouter = t.router({
post: t.router({
list: t.procedure.query(() => [{ id: '1', title: 'Hello' }]),
}),
});
export type AppRouter = typeof appRouter;
// @filename: utils/trpc.ts
// ---cut---
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/router';
export const trpc = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: 'YOUR_API_URL' })],
});
// @filename: server/router.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
const appRouter = t.router({
post: t.router({
list: t.procedure.query(() => [{ id: '1', title: 'Hello' }]),
}),
});
export type AppRouter = typeof appRouter;
// @filename: utils/trpc.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/router';
export const trpc = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: 'YOUR_API_URL' })],
});
// @filename: component.tsx
// ---cut---
import { useQuery } from '@tanstack/react-query';
import { trpc } from './utils/trpc';
function PostList() {
const { data } = useQuery({
queryKey: ['posts'] as const,
queryFn: () => trpc.post.list.query(),
});
data; // Post[]
// ...
}
The following dependencies should be installed
import { InstallSnippet } from '@site/src/components/InstallSnippet'; import TabItem from '@theme/TabItem'; import Tabs from '@theme/Tabs';
<InstallSnippet pkgs="@trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query" />:::tip AI Agents If you use an AI coding agent, install tRPC skills for better code generation:
npx @tanstack/intent@latest install
:::
AppRouter// @filename: server/router.ts
// ---cut---
import { initTRPC } from '@trpc/server';
import { z } from "zod";
const t = initTRPC.create();
const appRouter = t.router({
getUser: t.procedure.input(z.object({ id: z.string() })).query(() => ({ name: 'foo' })),
createUser: t.procedure.input(z.object({ name: z.string() })).mutation(() => 'bar'),
});
export type AppRouter = typeof appRouter;
// @filename: utils/trpc.ts
// ---cut---
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from '../server/router';
export const { TRPCProvider, useTRPC, useTRPCClient } = createTRPCContext<AppRouter>();
import ImportAppRouter from '../../partials/_import-approuter.mdx';
<ImportAppRouter />In cases where you rely on React context, such as when using server-side rendering in full-stack frameworks like Next.js, it's important to create a new QueryClient for each request so that your users don't end up sharing the same cache, you can use the createTRPCContext to create a set of type-safe context providers and consumers from your AppRouter type signature.
// @include: router
// @include: utils-a
Then, create a tRPC client, and wrap your application in the TRPCProvider, as below. You will also need to set up and connect React Query, which they document in more depth.
:::tip
If you already use React Query in your application, you should re-use the QueryClient and QueryClientProvider you already have. You can read more about the QueryClient initialization in the React Query docs.
:::
// @jsx: react-jsx
// @include: router
// @include: utils-a
// @filename: components/App.tsx
// ---cut---
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import { useState } from 'react';
import type { AppRouter } from '../server/router';
import { TRPCProvider } from '../utils/trpc';
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
});
}
let browserQueryClient: QueryClient | undefined = undefined;
function getQueryClient() {
if (typeof window === 'undefined') {
// Server: always make a new query client
return makeQueryClient();
} else {
// Browser: make a new query client if we don't already have one
// This is very important, so we don't re-make a new client if React
// suspends during the initial render. This may not be needed if we
// have a suspense boundary BELOW the creation of the query client
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
}
export function App() {
const queryClient = getQueryClient();
const [trpcClient] = useState(() =>
createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:2022',
}),
],
}),
);
return (
<QueryClientProvider client={queryClient}>
<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
{null /* Your app here */}
</TRPCProvider>
</QueryClientProvider>
);
}
If you want to prefix all queries and mutations with a specific key, see Query Key Prefixing for setup and usage examples.
When building an SPA using only client-side rendering with something like Vite, you can create the QueryClient and tRPC client outside of React context as singletons.
// @include: router
// @filename: utils/trpc.ts
// ---cut---
import { QueryClient } from '@tanstack/react-query';
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
import type { AppRouter } from '../server/router';
export const queryClient = new QueryClient();
const trpcClient = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:2022' })],
});
export const trpc = createTRPCOptionsProxy<AppRouter>({
client: trpcClient,
queryClient,
});
// @include: router
// @filename: utils/trpc.ts
import { QueryClient } from '@tanstack/react-query';
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
import type { AppRouter } from '../server/router';
export const queryClient = new QueryClient();
const trpcClient = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:2022' })],
});
export const trpc = createTRPCOptionsProxy<AppRouter>({
client: trpcClient,
queryClient,
});
// @filename: components/App.tsx
import React from 'react';
// ---cut---
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from '../utils/trpc';
export function App() {
return (
<QueryClientProvider client={queryClient}>
</QueryClientProvider>
);
}
You can now use the tRPC React Query integration to call queries and mutations on your API.
// @jsx: react-jsx
// @include: router
// @include: utils-a
// @filename: components/user-list.tsx
// ---cut---
import { useMutation, useQuery } from '@tanstack/react-query';
import { useTRPC } from '../utils/trpc';
export default function UserList() {
const trpc = useTRPC(); // use `import { trpc } from './utils/trpc'` if you're using the singleton pattern
const userQuery = useQuery(trpc.getUser.queryOptions({ id: 'id_bilbo' }));
const userCreator = useMutation(trpc.createUser.mutationOptions());
return (
<div>
<p>{userQuery.data?.name}</p>
<button onClick={() => userCreator.mutate({ name: 'Frodo' })}>
Create Frodo
</button>
</div>
);
}