@trpc/tanstack-react-query
react-query-setup
Set up @trpc/tanstack-react-query with createTRPCContext(), TRPCProvider and the useTRPC() hook, or createTRPCOptionsProxy() without React context. Pass queryOptions/infiniteQueryOptions/mutationOptions/ subscriptionOptions factories to TanStack Query hooks, invalidate with queryClient.invalidateQueries and queryFilter/pathFilter, read and write the cache with queryKey, prefix keys with keyPrefix, and infer types with inferInput/inferOutput.
TanStack React Query
Set up the TanStack React Query integration with or without React context, then query, mutate and invalidate
From https://trpc.io/docs/client/tanstack-react-query/setup
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, read the announcement post 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
❓ Do I have to use an integration?
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.
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/router';
export const trpc = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: 'YOUR_API_URL' })],
});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[]
// ...
}Setup
1. Install dependencies
The following dependencies should be installed
npm install @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query2. Import your AppRouter
Import your AppRouter type into the client application. This type holds the shape of your entire API.
import type { AppRouter } from '../server/router';Tip
By using import type you ensure that the reference will be stripped at compile-time, meaning you don't inadvertently import server-side code into your client. For more information, see the Typescript docs.
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.
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from '../server/router';
export const { TRPCProvider, useTRPC, useTRPCClient } = createTRPCContext<AppRouter>();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.
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.
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,
});import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from '../utils/trpc';
export function App() {
return (
<QueryClientProvider client={queryClient}>
{/* Your app here */}
</QueryClientProvider>
);
}4. Fetch data
You can now use the tRPC React Query integration to call queries and mutations on your API.
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>
);
}5. Invalidate queries after a mutation
There is no tRPC-specific cache API: use TanStack's QueryClient with the keys and filters from trpc. queryFilter() matches one procedure, for every input if you leave the input out, pathFilter() matches every query under a router, and trpc.pathFilter() matches every tRPC query. See Usage for all of them.
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useTRPC } from '../utils/trpc';
export function CreateUser() {
const trpc = useTRPC();
const queryClient = useQueryClient();
const userCreator = useMutation(
trpc.user.create.mutationOptions({
onSuccess: async (user) => {
// Write the new user into the cache...
queryClient.setQueryData(trpc.user.byId.queryKey({ id: user.id }), user);
// ...and refetch every query under the `user` router
await queryClient.invalidateQueries(trpc.user.pathFilter());
},
}),
);
return (
<button onClick={() => userCreator.mutate({ name: 'Frodo' })}>
Create Frodo
</button>
);
}Common mistakes
- Calling queryFilter() on a router. queryOptions, queryKey and queryFilter only exist on query procedures. For a router, use pathKey() and pathFilter().
- Writing query keys by hand. A useQuery({ queryKey: ['user', id], queryFn }) that calls the tRPC client works, but its key isn't tRPC's, so queryFilter()/pathFilter() invalidations and server-prefetched data never match it. Use the trpc factories instead.
- Using the classic hooks. trpc.user.byId.useQuery() and trpc.useUtils() belong to the classic @trpc/react-query client. In this package, pass trpc.*.queryOptions() to TanStack's hooks and use useQueryClient() for the cache. See Migrating.
- Calling useTRPC() outside the provider. It throws useTRPC() can only be used inside of a <TRPCProvider>. Mount TRPCProvider, with the same queryClient you give QueryClientProvider, above every component that calls useTRPC(). The singleton setup has no provider: import trpc instead.
- Sharing one QueryClient across server requests. A module-level new QueryClient() is fine in a client-only SPA, but with server rendering it leaks one user's cache into another's. Create one per request on the server, as getQueryClient() does above.
References
- TanStack React Query: The options and key factories on the trpc proxy, such as queryOptions, infiniteQueryOptions, mutationOptions, subscriptionOptions, queryKey, queryFilter and pathFilter, plus key prefixing and type inference
- Set up with React Server Components: Prefetch tRPC queries in React Server Components, hydrate them into client components, and call procedures directly on the server
Related skills
- client-setup (https://trpc.io/docs/client/vanilla/setup): npx @tanstack/intent@latest load @trpc/client#client-setup
- links (https://trpc.io/docs/client/links): npx @tanstack/intent@latest load @trpc/client#links