@trpc/tanstack-react-query
Version:
TanStack React Query Integration for tRPC
275 lines (206 loc) • 12.7 kB
Markdown
---
name: react-query-setup
description: Set up /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.
requires:
- client-setup
- links
sources:
- www/v11/docs/client/tanstack-react-query/setup.md
- www/v11/docs/client/tanstack-react-query/usage.md
- www/v11/docs/client/tanstack-react-query/server-components.md
metadata:
type: framework
library: trpc
library_version: 11.25.0
---
<!-- Generated from www/v11/docs/client/tanstack-react-query/setup.md by www/build/skills.ts. Edit the docs instead. -->
# 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](https://trpc.io/docs/client/react) 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="https://trpc.io/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](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 `/react-query` using just a [vanilla tRPC client](https://trpc.io/docs/client/vanilla), 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 no-check title="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' })],
});
```
```tsx no-check title="components/PostList.tsx"
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
```bash
npm install /server @trpc/client /tanstack-react-query @tanstack/react-query
```
### 2. Import your `AppRouter`
Import your `AppRouter` type into the client application. This type holds the shape of your entire API.
```ts no-check title="utils/trpc.ts"
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](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export).
### 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 title="utils/trpc.ts" no-check
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](https://tanstack.com/query/latest/docs/framework/react/quick-start).
> [!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](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr#initial-setup).
```tsx no-check title="components/App.tsx"
import { QueryClient, QueryClientProvider } from '/react-query';
import { createTRPCClient, httpBatchLink } from '/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](https://trpc.io/docs/client/tanstack-react-query/usage#keyPrefix) 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 no-check title="utils/trpc.ts"
import { QueryClient } from '/react-query';
import { createTRPCClient, httpBatchLink } from '/client';
import { createTRPCOptionsProxy } from '/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 no-check title="components/App.tsx"
import { QueryClientProvider } from '/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.
```tsx no-check title="components/user-list.tsx"
import { useMutation, useQuery } from '/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>
);
}
```
> [!TIP]
> A component that starts its own query can only fetch once it renders. If a route loader or server component knows the input, start the query there with `await trpc.getUser.unstable_$ensure({ id })`. It returns a **handle**: pass it to the component, which reads the query with `useQuery(trpc.unstable_$fromHandle(props.user))`, without repeating the procedure or its input. The handle only names the query, so the data must be in the browser's cache. A loader in the browser already shares that cache. A server has to send its cache along, for example with [`HydrateClient`](https://trpc.io/docs/client/tanstack-react-query/handles#hydrate-client) in Next.js. With no loader, start it at the top of the page with `unstable_$prefetch` on `useTRPC()`. See [Prefetching with handles](https://trpc.io/docs/client/tanstack-react-query/handles).
### 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](https://trpc.io/docs/client/tanstack-react-query/usage) for all of them.
```tsx no-check title="components/create-user.tsx"
import { useMutation, useQueryClient } from '/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 `/react-query` client](https://trpc.io/docs/client/react). In this package, pass `trpc.*.queryOptions()` to TanStack's hooks and use `useQueryClient()` for the cache. See [Migrating](https://trpc.io/docs/client/tanstack-react-query/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](https://trpc.io/docs/client/tanstack-react-query/setup#3c-set-up-without-react-context) 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](references/client-tanstack-react-query-usage.md): 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](references/client-tanstack-react-query-server-components.md): 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`