Example: QueryClient factory for RSC
```tsx
// trpc/query-client.ts
import {
defaultShouldDehydrateQuery,
QueryClient,
} from '@tanstack/react-query';
import superjson from 'superjson';
export function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 30 * 1000,
},
dehydrate: {
shouldDehydrateQuery: (query) =>
defaultShouldDehydrateQuery(query) ||
query.state.status === 'pending',
},
hydrate: {
},
},
});
}
```
This example shows configuring a QueryClient factory with appropriate defaults for server-side rendering.
Classic React Query integration with RSC is deprecated
The classic React Query integration for React Server Components is still supported but is not the recommended way to start new tRPC projects. The new TanStack React Query integration should be used instead.
Mount TRPCProvider at app root
The TRPCProvider should be mounted at the root of the application (e.g., app/layout.tsx in Next.js) to make tRPC hooks available throughout client components.
Invoke procedures directly in server components without prefetch
To get data directly in a server component, invoke the procedure without using .prefetch(), similar to using the normal server caller. For example: const greeting = await trpc.hello();
HydrateClient wrapper for hydrating prefetched data
Wrap client components that consume prefetched data with HydrateClient in the server component. This component, exported from trpc/server.tsx, hydrates the query client with prefetched data for use in client components.
Prefetch queries in server components using .prefetch()
In server components, use the trpc caller to prefetch queries by calling .prefetch() on procedures. This starts the request as soon as possible without suspending until the data is needed by downstream client components.
useQuery hook for standard query handling
In client components, use the trpc hooks (e.g., trpc.hello.useQuery()) to execute queries. The hook returns a promise-like object with a data property and loading state.
useSuspenseQuery hook for Suspense-based loading
Use trpc.hello.useSuspenseQuery() in client components to integrate with Suspense boundaries and Error Boundaries for cleaner loading and error handling. This hook suspends while data is loading instead of returning a loading state object.
Data retrieved in server components is not cached for client
When invoking a procedure directly in a server component (without .prefetch()), the data is not stored in the query client cache and cannot be accessed by client components. This separation is intentional and follows React Query's data ownership principles.
trpc/client.tsx must be marked with 'use client'
The trpc/client.tsx file must include 'use client' directive at the top to ensure it can be imported and used from server components while keeping the TRPCProvider marked as a client component.
Example: RSC setup with prefetch and HydrateClient
```tsx
// app/page.tsx
import { trpc, HydrateClient } from '../trpc/server';
import { ClientGreeting } from './client-greeting';
export default async function Home() {
void trpc.hello.prefetch();
return (
<HydrateClient>
<div>...</div>
<ClientGreeting />
</HydrateClient>
);
}
// app/client-greeting.tsx
'use client';
import { trpc } from '../trpc/client';
export function ClientGreeting() {
const greeting = trpc.hello.useQuery();
if (!greeting.data) return <div>Loading...</div>;
return <div>{greeting.data.greeting}</div>;
}
```
This example shows prefetching in a server component and consuming the data in a client component with useQuery.
RSC integration requires @trpc/react-query/rsc module
The RSC integration for tRPC is provided by the @trpc/react-query/rsc module, which exports createHydrationHelpers for integrating with React Query's server rendering capabilities.
Example: Getting data directly in server component
```tsx
// app/page.tsx
import { trpc } from '../trpc/server';
export default async function Home() {
const greeting = await trpc.hello();
return <div>{greeting.greeting}</div>;
}
```
This example shows invoking a procedure directly in a server component without prefetch. The data is not cached for use in client components.
Example: Server-side tRPC setup with createHydrationHelpers
```tsx
// trpc/server.tsx
import 'server-only';
import { createHydrationHelpers } from '@trpc/react-query/rsc';
import { cache } from 'react';
import { createCallerFactory, createTRPCContext } from './init';
import { makeQueryClient } from './query-client';
import { appRouter } from './routers/_app';
export const getQueryClient = cache(makeQueryClient);
const caller = createCallerFactory(appRouter)(createTRPCContext);
export const { trpc, HydrateClient } = createHydrationHelpers<typeof appRouter>(
caller,
getQueryClient,
);
```
This example shows setting up server-side helpers for prefetching and hydrating data in RSC.
QueryClient default options for RSC
When configuring a QueryClient for React Server Components, set staleTime to a value greater than 0 (e.g., 30 * 1000 milliseconds) to avoid refetching immediately on the client. Set shouldDehydrateQuery to extend defaultShouldDehydrateQuery to include queries still in pending state, allowing prefetching in server components to be consumed by client components downstream. If using a data transformer, configure serializeData and deserializeData options to ensure data is serialized correctly when hydrating over the server-client boundary.
createTRPCReact creates type-safe hooks from AppRouter type
Use createTRPCReact passing the AppRouter type to generate type-safe hooks for consuming tRPC API from client components. This is imported from @trpc/react-query.
TRPCProvider setup with singleton QueryClient pattern
When creating a TRPCProvider component, use a singleton pattern for the QueryClient in the browser to keep the same query client instance across renders. On the server (when typeof window === 'undefined'), always create a new QueryClient. Initialize the trpcClient using trpc.createClient() with httpBatchLink, wrapped in useState to avoid recreating it on every render.
getUrl function for tRPC endpoint
When setting up httpBatchLink, use a getUrl function that returns an empty string for client-side calls, uses process.env.VERCEL_URL for production deployment, and falls back to http://localhost:3000 for development. Append '/api/trpc' to the base URL to form the complete endpoint.
Avoid useState when initializing QueryClient with Suspense
When initializing the QueryClient in TRPCProvider, avoid using useState without a suspense boundary between the provider and code that may suspend. React will throw away the client on initial render if suspension occurs without a boundary.
createHydrationHelpers for RSC integration
The @trpc/react-query/rsc module exports createHydrationHelpers, a wrapper around createCaller that integrates with your React Query client. It takes the caller and getQueryClient function, returning trpc and HydrateClient exports for use in server components.
getQueryClient must be stable using React cache
When setting up server-side tRPC helpers, create a stable getter for the QueryClient using React's cache() function. This ensures the same client is returned during the same request: export const getQueryClient = cache(makeQueryClient).
Server component file must use 'server-only' import
When creating the trpc/server.tsx file for server components, import 'server-only' at the top to ensure the file cannot be imported from the client.
Example: login mutation with Zod validation and useMutation hook
```tsx
// Server: define mutation
const appRouter = t.router({
login: t.procedure
.input(z.object({ name: z.string() }))
.mutation((opts) => {
return { user: { name: opts.input.name, role: 'ADMIN' as const } };
}),
});
// Client: use mutation
const mutation = trpc.login.useMutation();
const handleLogin = () => {
mutation.mutate({ name: 'John Doe' });
};
return (
<button onClick={handleLogin} disabled={mutation.isPending}>
Login
</button>
);
```
This example shows a complete login mutation flow with input validation and client-side usage.
useMutation basic usage with tRPC
Call trpc.<procedure>.useMutation() to create a mutation hook for a tRPC mutation procedure. Then call mutation.mutate({ ...input }) to execute the mutation. The hook returns an object with properties including isPending (boolean indicating if the mutation is in progress) and error (contains error.message if the mutation failed).
useMutation hook wraps React Query mutations
The hooks provided by @trpc/react-query are thin wrappers around @tanstack/react-query. For in-depth information about options and usage patterns, refer to the @tanstack/react-query documentation on mutations.
useQueries with custom React Query context
You can pass an optional React Query context to useQueries as the second parameter to override the default context. The syntax is: trpc.useQueries((t) => [...queries...], myCustomContext).
useQueries with options example
const Component = () => {
const [post, greeting] = trpc.useQueries((t) => [
t.post.byId({ id: '1' }, { enabled: false }),
t.greeting({ text: 'world' }),
]);
const onButtonClick = () => {
post.refetch();
};
return (
<div>
<h1>{post.data && post.data.title}</h1>
<p>{greeting.data?.message}</p>
<button onClick={onButtonClick}>Click to fetch</button>
</div>
);
};
useQueries hook for multiple queries
The useQueries hook fetches a variable number of queries at the same time using only one hook call. It is based on the @tanstack/query useQueries hook, with the difference that instead of passing an object with a queries array, you pass a callback function that receives a t proxy and returns an array of queries. When using httpBatchLink or wsLink, multiple queries will result in only 1 HTTP call to the server.
useQueries main use case
The main use case for useQueries is to fetch a number of queries, usually of the same type. For example, if you fetch a list of todo ids, you can then map over them in a useQueries hook calling a byId endpoint that would fetch the details of each todo.
useQueries with multiple types vs suspense
While fetching multiple types in a useQueries hook is possible, there is not much of an advantage compared to using multiple useQuery calls unless you use the suspense option. The useQueries hook can trigger suspense in parallel while multiple useQuery calls would waterfall.
useQueries basic example
const Component = (props: { postIds: string[] }) => {
const postQueries = trpc.useQueries((t) =>
props.postIds.map((id) => t.post.byId({ id })),
);
return <>{/* [...] */}</>;
};
useQueries with query options
You can pass any normal React Query options to the second parameter of any of the query calls in the array such as enabled, suspense, refetchOnWindowFocus, and others. These options work the same as in the standard @tanstack/query useQuery hook.
useQueries automatic batching with httpBatchLink and wsLink
When using httpBatchLink or wsLink, multiple queries in useQueries will end up being only 1 HTTP call to your server. Additionally, if the underlying procedure uses something like Prisma's findUnique(), it will automatically batch and do exactly 1 database query.
useQueries with custom context example
const [post, greeting] = trpc.useQueries(
(t) => [t.post.byId({ id: '1' }), t.greeting({ text: 'world' })],
myCustomContext,
);
useQuery with undefined input and options
If you need to set useQuery options but don't want to pass any input, you can pass undefined as the input parameter instead of skipping it.
useQuery autocompletion on input
useQuery provides autocompletion on the input parameter based on the input schema defined on the backend procedure.
useQuery streaming responses with async generators
Since v11, useQuery supports streaming queries when using httpBatchStreamLink. When a query returns an async generator, the results appear in the data property as an array that updates as the response comes in. The status becomes 'success' as soon as the first chunk is received, and fetchStatus is 'fetching' until the last chunk is received.
useQuery streaming state progression table
During streaming with useQuery, the result properties progress through these states: (status='pending', fetchStatus='fetching', data=undefined) → (status='success', fetchStatus='fetching', data=[]) → (status='success', fetchStatus='fetching', data=[0]) → (status='success', fetchStatus='fetching', data=[0, 1]) → (status='success', fetchStatus='fetching', data=[0, 1, 2]) → (status='success', fetchStatus='idle', data=[0, 1, 2]).
useQuery basic example with optional input
Example: const helloNoArgs = trpc.hello.useQuery(); const helloWithArgs = trpc.hello.useQuery({ text: 'client' }); When input is optional, you can call useQuery without passing an input argument.
useQuery streaming example with async generator
Server example: const appRouter = t.router({ iterable: t.procedure.query(async function* () { for (let i = 0; i < 3; i++) { await new Promise((resolve) => setTimeout(resolve, 500)); yield i; } }) }); Client usage: const result = trpc.iterable.useQuery(); return <div>{result.data?.map((chunk, index) => <Fragment key={index}>{chunk}</Fragment>)}</div>;
UseTRPCQueryOptions interface with trpc-specific options
UseTRPCQueryOptions extends @tanstack/react-query's UseQueryOptions and includes three trpc-specific options: trpc.ssr (boolean, optional) to override global SSR config per procedure; trpc.abortOnUnmount (boolean, optional) to override global config for aborting queries on unmount; trpc.context (Record<string, unknown>, optional) to add extra metadata for use in Links.
useQuery hook signature and interface
The useQuery function signature is: declare function useQuery(input: TInput | SkipToken, opts?: UseTRPCQueryOptions): void. UseTRPCQueryOptions extends @tanstack/react-query's UseQueryOptions and includes trpc-specific options. The input parameter can be a value or a SkipToken symbol.
useQuery SSR option behavior
The trpc.ssr option in useQuery allows disabling SSR for a particular query if global config has ssr: true. However, you cannot enable SSR on a procedure if the global config is set to false.
Migrate invalidations from classic to new client
In the classic client, invalidations use 'utils.greeting.invalidate({ name: 'Jerry' })'. In the new client, use the TanStack React Query queryClient directly: 'queryClient.invalidateQueries(trpc.greeting.queryFilter({ name: 'Jerry' }))' after obtaining the queryClient with 'const queryClient = useQueryClient()'.
Query filter helper for TanStack React Query
The new client provides 'trpc.procedure.queryFilter()' helper to create query filters compatible with TanStack React Query's queryClient methods. This is used with methods like 'queryClient.invalidateQueries()' instead of the classic client's 'useUtils()' pattern.
New client uses TanStack React Query hooks directly
The new client is built on top of TanStack React Query. Instead of tRPC-specific hooks like 'useQuery' and 'useMutation', you use the TanStack React Query hooks 'useQuery' and 'useMutation' with options generated by 'trpc.procedure.queryOptions()' or 'trpc.procedure.mutationOptions()'.
createTRPCContext setup for new client
Set up the new client by importing createTRPCContext from '@trpc/tanstack-react-query' and calling it with the AppRouter type: 'const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>()'. This returns a TRPCProvider component for wrapping your app and a useTRPC hook for accessing procedures in components.
Migrate mutations from classic to new client
In the classic client, mutations use 'trpc.createUser.useMutation()'. In the new client, use 'useMutation(trpc.createUser.mutationOptions())' after creating the context with 'const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>()' and calling 'const trpc = useTRPC()' in the component.
Codemod CLI for migration
Run 'npx @trpc/upgrade' to use the codemod tool for migrating from the classic React Client. When prompted, select the transforms 'Migrate Hooks to xxxOptions API' and 'Migrate context provider setup'. The codemod is a work in progress and contributions are welcome.
New and classic clients are compatible
The new TanStack React Query client and the classic tRPC client are compatible with each other and can live together in the same application. This enables gradual migration by using the new client in new parts of the application while maintaining existing usage. Query Keys are identical between the two clients, so TanStack Query's caching works across both.
Migrate queries from classic to new client
In the classic client, queries use 'trpc.greeting.useQuery({ name: 'Jerry' })'. In the new client, use 'useQuery(trpc.greeting.queryOptions({ name: 'Jerry' }))' after creating the context with 'const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>()' and calling 'const trpc = useTRPC()' in the component.
useUtils example with router-level and full invalidation
Example showing different levels of invalidation:
```tsx
import { trpc } from './utils/trpc';
function MyComponent() {
const utils = trpc.useUtils();
const invalidateAllQueriesAcrossAllRouters = () => {
utils.invalidate();
};
const invalidateAllPostQueries = () => {
utils.post.invalidate();
};
const invalidatePostById = () => {
utils.post.byId.invalidate({ id: 1 });
};
trpc.user.all.useQuery(); // Only invalidated by invalidateAllQueriesAcrossAllRouters()
trpc.post.all.useQuery(); // Invalidated by invalidateAllQueriesAcrossAllRouters() & invalidateAllPostQueries()
trpc.post.byId.useQuery({ id: 1 }); // Invalidated by all three
trpc.post.byId.useQuery({ id: 2 }); // Invalidated by first two only
}
```
useUtils hook overview
useUtils is a hook that gives you access to helpers to manage cached data of queries executed via @trpc/react-query. These helpers are thin wrappers around @tanstack/react-query's queryClient methods. The hook was called useContext() until version 10.41.0 and is still aliased for backwards compatibility.
useUtils returns router structure
useUtils returns an object with all available queries from your routers, mirroring your tRPC client object structure. When you navigate to a specific query (e.g., utils.post.all), you gain access to query helpers for that procedure.
useUtils helper methods reference
useUtils provides the following helper methods that wrap @tanstack/react-query methods: fetch (wraps queryClient.fetchQuery), prefetch (queryClient.prefetchQuery), fetchInfinite (queryClient.fetchInfiniteQuery), prefetchInfinite (queryClient.prefetchInfiniteQuery), ensureData (queryClient.ensureData), invalidate (queryClient.invalidateQueries), refetch (queryClient.refetchQueries), cancel (queryClient.cancelQueries), setData (queryClient.setQueryData), setQueriesData (queryClient.setQueriesData), getData (queryClient.getQueryData), setInfiniteData (queryClient.setInfiniteQueryData), getInfiniteData (queryClient.getInfiniteData), setMutationDefaults (queryClient.setMutationDefaults), getMutationDefaults (queryClient.getMutationDefaults), isMutating (queryClient.isMutating), and reset (queryClient.resetQueries).
useUtils proxy client for async/await calls
useUtils exposes a proxy client (utils.client) that lets you call your procedures with async/await without creating an additional vanilla client. This allows you to call mutations like await utils.client.apiKey.create.mutate() directly.
invalidate helper available at every router level
The invalidate helper is special and available at every level of the router map. This means you can call invalidate on a single query, a whole router, or every router in the application.
Invalidate single query with input filtering
You can invalidate a query for a single procedure and filter based on input passed to it to prevent unnecessary backend calls. For example, utils.post.byId.invalidate({ id: input.id }) will only invalidate queries with that specific id, leaving other id's unaffected.
Invalidate entire router
You can invalidate all queries within a specific router using invalidate at the router level. For example, utils.post.invalidate() invalidates all queries in the post router, while utils.invalidate() invalidates all queries across all routers.
Invalidate full cache on every mutation
You can configure createTRPCReact with an overrides option that invalidates the full cache as a side-effect on any mutation. This is added through the useMutation override with an onSuccess handler that calls opts.queryClient.invalidateQueries(). Request batching ensures all queries on the page refetch in a single request.