Back to Trpc

TanStack React Query

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

11.16.030.9 KB
Original Source

Quick example query

tsx
// @filename: server/router.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  greeting: t.procedure.input(z.object({ name: z.string() })).query(({ input }) => `Hello ${input.name}` as const),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server/router';
export const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.tsx
// ---cut---
import { useQuery } from '@tanstack/react-query';
import { useTRPC } from './trpc';

function Users() {
  const trpc = useTRPC();

  const greetingQuery = useQuery(trpc.greeting.queryOptions({ name: 'Jerry' }));

  // greetingQuery.data === 'Hello Jerry'
}

Usage

The philosophy of this client is to provide thin and type-safe factories which work natively and type-safely with Tanstack React Query. This means just by following the autocompletes the client gives you, you can focus on building just with the knowledge the TanStack React Query docs provide.

tsx
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({
    query: t.procedure.input(z.object({ id: z.string().optional() }).optional()).query(() => 'result'),
    mutation: t.procedure.mutation(() => 'ok'),
  }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useTRPC } from './trpc';
// ---cut---
export default function Basics() {
  const trpc = useTRPC();
  const queryClient = useQueryClient();

  // Create QueryOptions which can be passed to query hooks
  const myQueryOptions = trpc.path.to.query.queryOptions({ /** inputs */ })
  const myQuery = useQuery(myQueryOptions)
  // or:
  // useSuspenseQuery(myQueryOptions)
  // useInfiniteQuery(myQueryOptions)

  // Create MutationOptions which can be passed to useMutation
  const myMutationOptions = trpc.path.to.mutation.mutationOptions()
  const myMutation = useMutation(myMutationOptions)

  // Create a QueryKey which can be used to manipulate many methods
  // on TanStack's QueryClient in a type-safe manner
  const myQueryKey = trpc.path.to.query.queryKey()

  const invalidateMyQueryKey = () => {
    queryClient.invalidateQueries({ queryKey: myQueryKey })
  }

  return (
    // Your app here
    null
  )
}

The trpc object is fully type-safe and will provide autocompletes for all the procedures in your AppRouter. At the end of the proxy, the following methods are available:

queryOptions - querying data {#queryOptions}

Available for all query procedures. Provides a type-safe wrapper around Tanstack's queryOptions function. The first argument is the input for the procedure, and the second argument accepts any native Tanstack React Query options.

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({ query: t.procedure.input(z.object({ id: z.string() })).query(() => 'result') }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
// ---cut---
const queryOptions = trpc.path.to.query.queryOptions(
  {
    /** input */
    id: 'foo',
  },
  {
    // Any Tanstack React Query options
    staleTime: 1000,
  },
);
// ---cut-after---
}

You can additionally provide a trpc object to the queryOptions function to provide tRPC request options to the client.

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({ query: t.procedure.input(z.object({ id: z.string() })).query(() => 'result') }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
// ---cut---
const queryOptions = trpc.path.to.query.queryOptions(
  {
    /** input */
    id: 'foo',
  },
  {
    trpc: {
      // Provide tRPC request options to the client
      context: {
        // see https://trpc.io/docs/client/links#managing-context
      },
    },
  },
);
// ---cut-after---
}

If you want to disable a query in a type safe way, you can use skipToken:

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  user: t.router({
    details: t.procedure.input(z.object({ userId: z.string(), projectId: z.string() })).query(() => ({ name: 'foo' })),
  }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useQuery, skipToken } from '@tanstack/react-query';
import { useTRPC } from './trpc';
declare const user: { id: string } | undefined;
declare const project: { id: string } | undefined;
function Component() {
const trpc = useTRPC();
// ---cut---
const query = useQuery(
  trpc.user.details.queryOptions(
    user?.id && project?.id
      ? {
          userId: user.id,
          projectId: project.id,
        }
      : skipToken,
    {
      staleTime: 1000,
    },
  ),
);
// ---cut-after---
}

The result can be passed to useQuery or useSuspenseQuery hooks or query client methods like fetchQuery, prefetchQuery, prefetchInfiniteQuery, invalidateQueries, etc.

infiniteQueryOptions - querying infinite data {#infiniteQueryOptions}

Available for all query procedures that take a cursor input. Provides a type-safe wrapper around Tanstack's infiniteQueryOptions function. The first argument is the input for the procedure, and the second argument accepts any native Tanstack React Query options.

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({
    query: t.procedure.input(z.object({ cursor: z.number().optional() })).query(() => ({ items: ['item'], nextCursor: 1 as number | undefined })),
  }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
// ---cut---
const infiniteQueryOptions = trpc.path.to.query.infiniteQueryOptions(
  {
    /** input */
  },
  {
    // Any Tanstack React Query options
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  },
);
// ---cut-after---
}

queryKey - getting the query key and performing operations on the query client {#queryKey}

Available for all query procedures. Allows you to access the query key in a type-safe manner.

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({ query: t.procedure.input(z.object({ id: z.string() })).query(() => 'result') }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
// ---cut---
const queryKey = trpc.path.to.query.queryKey();
// ---cut-after---
}

Since Tanstack React Query uses fuzzy matching for query keys, you can also create a partial query key for any sub-path to match all queries belonging to a router:

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
const appRouter = t.router({
  router: t.router({ someQuery: t.procedure.query(() => 'result') }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
// ---cut---
const queryKey = trpc.router.pathKey();
// ---cut-after---
}

Or even the root path to match all tRPC queries:

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
const appRouter = t.router({ someQuery: t.procedure.query(() => 'result') });
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
// ---cut---
const queryKey = trpc.pathKey();
// ---cut-after---
}

infiniteQueryKey - getting the infinite query key {#infiniteQueryKey}

Available for all query procedures that take a cursor input. Allows you to access the query key for an infinite query in a type-safe manner.

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({
    query: t.procedure.input(z.object({ cursor: z.number().optional() })).query(() => ({ items: ['item'], nextCursor: 1 as number | undefined })),
  }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
// ---cut---
const infiniteQueryKey = trpc.path.to.query.infiniteQueryKey({
  /** input */
});
// ---cut-after---
}

The result can be used with query client methods like getQueryData, setQueryData, invalidateQueries, etc.

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({
    query: t.procedure.input(z.object({ cursor: z.number().optional() })).query(() => ({ items: ['item'], nextCursor: 1 as number | undefined })),
  }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useQueryClient } from '@tanstack/react-query';
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
const queryClient = useQueryClient();
// ---cut---
// Get cached data for an infinite query
const cachedData = queryClient.getQueryData(
  trpc.path.to.query.infiniteQueryKey({ cursor: 0 }),
);

// Set cached data for an infinite query
queryClient.setQueryData(
  trpc.path.to.query.infiniteQueryKey({ cursor: 0 }),
  (data) => {
    // Modify the data
    return data;
  },
);
// ---cut-after---
}

queryFilter - creating query filters {#queryFilter}

Available for all query procedures. Allows creating query filters in a type-safe manner.

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({ query: t.procedure.input(z.object({ id: z.string() })).query(() => 'result') }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
// ---cut---
const queryFilter = trpc.path.to.query.queryFilter(
  {
    /** input */
  },
  {
    // Any Tanstack React Query filter
    predicate: (query) => {
      return !!query.state.data;
    },
  },
);
// ---cut-after---
}

Like with query keys, if you want to run a filter across a whole router you can use pathFilter to target any sub-path.

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ someQuery: t.procedure.query(() => 'result') }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
// ---cut---
const queryFilter = trpc.path.pathFilter({
  // Any Tanstack React Query filter
  predicate: (query) => {
    return !!query.state.data;
  },
});
// ---cut-after---
}

Useful for creating filters that can be passed to client methods like queryClient.invalidateQueries etc.

infiniteQueryFilter - creating infinite query filters {#infiniteQueryFilter}

Available for all query procedures that take a cursor input. Allows creating query filters for infinite queries in a type-safe manner.

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({
    query: t.procedure.input(z.object({ cursor: z.number().optional() })).query(() => ({ items: ['item'], nextCursor: 1 as number | undefined })),
  }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
// ---cut---
const infiniteQueryFilter = trpc.path.to.query.infiniteQueryFilter(
  {
    /** input */
  },
  {
    // Any Tanstack React Query filter
    predicate: (query) => {
      return !!query.state.data;
    },
  },
);
// ---cut-after---
}

Useful for creating filters that can be passed to client methods like queryClient.invalidateQueries etc.

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({
    query: t.procedure.input(z.object({ cursor: z.number().optional() })).query(() => ({ items: ['item'], nextCursor: 1 as number | undefined })),
  }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useQueryClient } from '@tanstack/react-query';
import { useTRPC } from './trpc';
async function Component() {
const trpc = useTRPC();
const queryClient = useQueryClient();
// ---cut---
await queryClient.invalidateQueries(
  trpc.path.to.query.infiniteQueryFilter(
    {},
    {
      predicate: (query) => {
        // Filter logic based on query state
        return query.state.status === 'success';
      },
    },
  ),
);
// ---cut-after---
}

mutationOptions - creating mutation options {#mutationOptions}

Available for all mutation procedures. Provides a type-safe identity function for constructing options that can be passed to useMutation.

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({
    mutation: t.procedure.input(z.object({ id: z.string() })).mutation(() => 'ok' as const),
  }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
// ---cut---
const mutationOptions = trpc.path.to.mutation.mutationOptions({
  // Any Tanstack React Query options
  onSuccess: (data) => {
    // do something with the data
  },
});
// ---cut-after---
}

mutationKey - getting the mutation key {#mutationKey}

Available for all mutation procedures. Allows you to get the mutation key in a type-safe manner.

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({
    mutation: t.procedure.input(z.object({ id: z.string() })).mutation(() => 'ok' as const),
  }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
function Component() {
const trpc = useTRPC();
// ---cut---
const mutationKey = trpc.path.to.mutation.mutationKey();
// ---cut-after---
}

subscriptionOptions - creating subscription options {#subscriptionOptions}

TanStack does not provide a subscription hook, so we continue to expose our own abstraction here which works with a standard tRPC subscription setup. Available for all subscription procedures. Provides a type-safe identity function for constructing options that can be passed to useSubscription. Note that you need to have either the httpSubscriptionLink or wsLink configured in your tRPC client to use subscriptions.

tsx
// @jsx: react-jsx
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({
    subscription: t.procedure.input(z.object({ channel: z.string().optional() }).optional()).subscription(async function* () {
      yield 'data' as string;
    }),
  }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.tsx
import { useTRPC } from './trpc';
import { useSubscription } from '@trpc/tanstack-react-query';
// ---cut---
function SubscriptionExample() {
  const trpc = useTRPC();
  const subscription = useSubscription(
    trpc.path.to.subscription.subscriptionOptions(
      {
        /** input */
      },
      {
        enabled: true,
        onStarted: () => {
          // do something when the subscription is started
        },
        onData: (data) => {
          // you can handle the data here
        },
        onError: (error) => {
          // you can handle the error here
        },
        onConnectionStateChange: (state) => {
          // you can handle the connection state here
        },
      },
    ),
  );

  // Or you can handle the state here
  subscription.data; // The lastly received data
  subscription.error; // The lastly received error

  /**
   * The current status of the subscription.
   * Will be one of: `'idle'`, `'connecting'`, `'pending'`, or `'error'`.
   *
   * - `idle`: subscription is disabled or ended
   * - `connecting`: trying to establish a connection
   * - `pending`: connected to the server, receiving data
   * - `error`: an error occurred and the subscription is stopped
   */
  subscription.status;

  // Reset the subscription (if you have an error etc)
  subscription.reset();

  return <></>;
}

Query Key Prefixing {#keyPrefix}

When using multiple tRPC providers in a single application (e.g., connecting to different backend services), queries with the same path will collide in the cache. You can prevent this by enabling query key prefixing.

tsx
// @filename: server.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
const authRouter = t.router({ list: t.procedure.query(() => [{ id: '1' }]) });
const billingRouter = t.router({ list: t.procedure.query(() => [{ id: '1' }]) });
export type AuthRouter = typeof authRouter;
export type BillingRouter = typeof billingRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AuthRouter, BillingRouter } from './server';
const auth = createTRPCContext<AuthRouter>();
const billing = createTRPCContext<BillingRouter>();
export const useTRPCAuth = auth.useTRPC;
export const useTRPCBilling = billing.useTRPC;

// @filename: component.ts
import { useQuery } from '@tanstack/react-query';
import { useTRPCAuth, useTRPCBilling } from './trpc';
function Component() {
const trpcAuth = useTRPCAuth();
const trpcBilling = useTRPCBilling();
// ---cut---
// Without prefixes - these would collide!
const authQuery = useQuery(trpcAuth.list.queryOptions()); // auth service
const billingQuery = useQuery(trpcBilling.list.queryOptions()); // billing service
// ---cut-after---
}

Enable the feature flag when creating your context:

tsx
// @filename: server.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
const billingRouter = t.router({ list: t.procedure.query(() => [{ id: '1' }]) });
const accountRouter = t.router({ list: t.procedure.query(() => [{ id: '1' }]) });
export type BillingRouter = typeof billingRouter;
export type AccountRouter = typeof accountRouter;

// @filename: utils/trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import { createTRPCClient } from '@trpc/client';
import type { BillingRouter, AccountRouter } from '../server';
// ---cut---
// [...]

const billing = createTRPCContext<BillingRouter, { keyPrefix: true }>();
export const BillingProvider = billing.TRPCProvider;
export const useBilling = billing.useTRPC;
export const createBillingClient = () =>
  createTRPCClient<BillingRouter>({
    links: [
      /* ... */
    ],
  });

const account = createTRPCContext<AccountRouter, { keyPrefix: true }>();
export const AccountProvider = account.TRPCProvider;
export const useAccount = account.useTRPC;
export const createAccountClient = () =>
  createTRPCClient<AccountRouter>({
    links: [
      /* ... */
    ],
  });
tsx
// @jsx: react-jsx
// @filename: server.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
const billingRouter = t.router({ list: t.procedure.query(() => [{ id: '1' }]) });
const accountRouter = t.router({ list: t.procedure.query(() => [{ id: '1' }]) });
export type BillingRouter = typeof billingRouter;
export type AccountRouter = typeof accountRouter;

// @filename: utils/trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import { createTRPCClient } from '@trpc/client';
import type { BillingRouter, AccountRouter } from '../server';
const billing = createTRPCContext<BillingRouter, { keyPrefix: true }>();
export const BillingProvider = billing.TRPCProvider;
export const createBillingClient = () => createTRPCClient<BillingRouter>({ links: [] });
const account = createTRPCContext<AccountRouter, { keyPrefix: true }>();
export const AccountProvider = account.TRPCProvider;
export const createAccountClient = () => createTRPCClient<AccountRouter>({ links: [] });

// @filename: App.tsx
// ---cut---
import { useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
  BillingProvider,
  AccountProvider,
  createBillingClient,
  createAccountClient,
} from './utils/trpc';

// [...]

export function App() {
  const [queryClient] = useState(() => new QueryClient());
  const [billingClient] = useState(() => createBillingClient());
  const [accountClient] = useState(() => createAccountClient());

  return (
    <QueryClientProvider client={queryClient}>
      <BillingProvider
        trpcClient={billingClient}
        queryClient={queryClient}
        keyPrefix="billing"
      >
        <AccountProvider
          trpcClient={accountClient}
          queryClient={queryClient}
          keyPrefix="account"
        >
          <div></div>
        </AccountProvider>
      </BillingProvider>
    </QueryClientProvider>
  );
}
tsx
// @jsx: react-jsx
// @filename: server.ts
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
const billingRouter = t.router({ list: t.procedure.query(() => [{ id: '1' }]) });
const accountRouter = t.router({ list: t.procedure.query(() => [{ id: '1' }]) });
export type BillingRouter = typeof billingRouter;
export type AccountRouter = typeof accountRouter;

// @filename: utils/trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { BillingRouter, AccountRouter } from '../server';
const billing = createTRPCContext<BillingRouter, { keyPrefix: true }>();
export const useBilling = billing.useTRPC;
const account = createTRPCContext<AccountRouter, { keyPrefix: true }>();
export const useAccount = account.useTRPC;

// @filename: components/MyComponent.tsx
// ---cut---
import { useQuery } from '@tanstack/react-query';
import { useBilling, useAccount } from '../utils/trpc';

// [...]

export function MyComponent() {
  const billing = useBilling();
  const account = useAccount();

  const billingList = useQuery(billing.list.queryOptions());
  const accountList = useQuery(account.list.queryOptions());

  return (
    <div>
      <div>Billing: {JSON.stringify(billingList.data ?? null)}</div>
      <div>Account: {JSON.stringify(accountList.data ?? null)}</div>
    </div>
  );
}

The query keys will be properly prefixed to avoid collisions:

tsx
// Example of how the query keys look with prefixes
const queryKeys = [
  [['billing'], ['list'], { type: 'query' }],
  [['account'], ['list'], { type: 'query' }],
];

Inferring Input and Output types

When you need to infer the input and output types for a procedure or router, there are 2 options available depending on the situation.

Infer the input and output types of a full router

ts
// @filename: server/router.ts
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' })),
});
export type AppRouter = typeof appRouter;

// @filename: types.ts
// ---cut---
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from './server/router';

export type Inputs = inferRouterInputs<AppRouter>;
export type Outputs = inferRouterOutputs<AppRouter>;

Infer types for a single procedure

ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({
    procedure: t.procedure.input(z.object({ id: z.string() })).query(() => ({ name: 'foo' })),
  }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server';
export const { useTRPC } = createTRPCContext<AppRouter>();

// @filename: component.ts
import { useTRPC } from './trpc';
// ---cut---
import type { inferInput, inferOutput } from '@trpc/tanstack-react-query';

function Component() {
  const trpc = useTRPC();

  type Input = inferInput<typeof trpc.path.to.procedure>;
  type Output = inferOutput<typeof trpc.path.to.procedure>;
}

Accessing the tRPC client {#useTRPCClient}

If you used the setup with React Context, you can access the tRPC client using the useTRPCClient hook.

tsx
// @filename: server/router.ts
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' })),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from './server/router';
export const { TRPCProvider, useTRPC, useTRPCClient } = createTRPCContext<AppRouter>();

// @filename: component.tsx
// ---cut---
import { useTRPCClient } from './trpc';

async function Component() {
  const trpcClient = useTRPCClient();

  const result = await trpcClient.getUser.query({
    id: '1',
  });
}

If you setup without React Context, you can import the global client instance directly instead.

ts
// @module: esnext
// @target: esnext
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
  path: t.router({ to: t.router({
    procedure: t.procedure.input(z.object({ id: z.string() })).query(() => ({ name: 'foo' })),
  }) }),
});
export type AppRouter = typeof appRouter;

// @filename: trpc.ts
import { createTRPCClient } from '@trpc/client';
import type { AppRouter } from './server';
export const client = createTRPCClient<AppRouter>({ links: [] });

// @filename: example.ts
// ---cut---
import { client } from './trpc';

const result = await client.path.to.procedure.query({
  /** input */
  id: 'foo',
});