Back to Trpc

TanStack React Query

www/docs/client/tanstack-react-query/setup.mdx

11.16.09.1 KB
Original Source

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.

ts
// @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' })],
});
tsx
// @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[]

  // ...
}
</details>

Setup

1. Install dependencies

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:

bash
npx @tanstack/intent@latest install

:::

2. Import your AppRouter

twoslash
// @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;
twoslash
// @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 />

3a. Set up the tRPC context provider

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.

tsx
// @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. :::

tsx
// @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>
  );
}

3b. Set up with Query/Mutation Key Prefixing enabled

If you want to prefix all queries and mutations with a specific key, see Query Key Prefixing for setup and usage examples.

3c. Set up without React context

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.

ts
// @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,
});
tsx
// @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>
  );
}

4. Fetch data

You can now use the tRPC React Query integration to call queries and mutations on your API.

tsx
// @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>
  );
}