UNPKG

@trpc/tanstack-react-query

Version:

TanStack React Query Integration for tRPC

275 lines (206 loc) • 12.7 kB
--- name: react-query-setup description: 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. 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 `@tanstack/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 @trpc/server @trpc/client @trpc/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 '@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](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 '@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 no-check title="components/App.tsx" 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. ```tsx no-check title="components/user-list.tsx" 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> ); } ``` > [!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 '@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](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`