skills/ trpc/trpc

react-query-setup

Set up @trpc/tanstack-react-query with createTRPCContext(), TRPCProvider, useTRPC() hook, queryOptions/mutationOptions factories, query invalidation via queryClient.invalidateQueries with queryFilter, and type inference with inferInput/inferOutput.

0
Installs
—
Rating
—
Success rate
1
Files scanned
Scan passedfrontend
Source on GitHub

Security scan

Scan passed

No risky patterns were found in the scanned files.

1 files scannedscanner v1.2.0Oct 11, 2026

Content sha256 01a43ca06309da65… — run codexguild_scan_skills after installing to verify your local copy.

Static analysis is a first line of defense, not a guarantee. Read the source

SKILL.md

exact scanned copy

This skill builds on [client-setup] and [links]. Read them first for foundational concepts.

tRPC -- TanStack React Query Setup

Setup

Install

npm install @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query

Create the tRPC context (with React context)

import { createTRPCContext } from '@trpc/tanstack-react-query';
import type { AppRouter } from '../server/router';

export const { TRPCProvider, useTRPC, useTRPCClient } =
  createTRPCContext<AppRouter>();

Wire up providers

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: {
        staleTime: 60 * 1000,
      },
    },
  });
}

let browserQueryClient: QueryClient | undefined = undefined;

function getQueryClient() {
  if (typeof window === 'undefined') {
    return makeQueryClient();
  }
  if (!browserQueryClient) browserQueryClient = makeQueryClient();
  return browserQueryClient;
}

export function App() {
  const queryClient = getQueryClient();
  const [trpcClient] = useState(() =>
    createTRPCClient<AppRouter>({
      links: [
        httpBatchLink({
          url: 'http://localhost:3000/api/trpc',
        }),
      ],
    }),
  );

  return (
    <QueryClientProvider client={queryClient}>
      <TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
        {/* Your app here */}
      </TRPCProvider>
    </QueryClientProvider>
  );
}

Alternative: setup without React context (SPA / Vite)

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:3000/api/trpc' })],
});

export const trpc = createTRPCOptionsProxy<AppRouter>({
  client: trpcClient,
  queryClient,
});

When using the singleton pattern, wrap your app with QueryClientProvider only (no TRPCProvider needed) and import trpc directly instead of calling useTRPC().

Core Patterns

queryOptions -- querying data

import { skipToken, useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { useTRPC } from '../utils/trpc';

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

  // Basic query
  const userQuery = useQuery(trpc.user.byId.queryOptions({ id: '1' }));

  // With TanStack Query options
  const staleQuery = useQuery(
    trpc.user.byId.queryOptions({ id: '1' }, { staleTime: 5000 }),
  );

  // Suspense query
  const { data } = useSuspenseQuery(trpc.user.byId.queryOptions({ id: '1' }));

  // Conditional query with skipToken
  const conditionalQuery = useQuery(
    trpc.user.byId.queryOptions(userId ? { id: userId } : skipToken),
  );

  return <div>{userQuery.data?.name}</div>;
}

mutationOptions -- writing data

import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useTRPC } from '../utils/trpc';

function CreateUser() {
  const trpc = useTRPC();
  const queryClient = useQueryClient();

  const createUser = useMutation(
    trpc.user.create.mutationOptions({
      onSuccess: () => {
        queryClient.invalidateQueries(trpc.user.queryFilter());
      },
    }),
  );

  return (
    <button onClick={() => createUser.mutate({ name: 'Alice' })}>Create</button>
  );
}

Query invalidation and cache manipulation

import { useQueryClient } from '@tanstack/react-query';
import { useTRPC } from '../utils/trpc';

function InvalidationExample() {
  const trpc = useTRPC();
  const queryClient = useQueryClient();

  // Invalidate a specific query
  const invalidateOne = () =>
    queryClient.invalidateQueries(trpc.user.byId.queryFilter({ id: '1' }));

  // Invalidate all queries under a router
  const invalidateAll = () =>
    queryClient.invalidateQueries(trpc.user.queryFilter());

  // Invalidate ALL tRPC queries
  const invalidateEverything = () =>
    queryClient.invalidateQueries({ queryKey: trpc.pathKey() });

  // Read/write cache directly
  const cached = queryClient.getQueryData(trpc.user.byId.queryKey({ id: '1' }));
  queryClient.setQueryData(trpc.user.byId.queryKey({ id: '1' }), {
    id: '1',
    name: 'Updated',
  });
}

infiniteQueryOptions -- paginated data

import { useInfiniteQuery } from '@tanstack/react-query';
import { useTRPC } from '../utils/trpc';

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

  const posts = useInfiniteQuery(
    trpc.post.list.infiniteQueryOptions(
      { limit: 10 },
      {
        getNextPageParam: (lastPage) => lastPage.nextCursor,
      },
    ),
  );

  return (
    <div>
      {posts.data?.pages.flatMap((page) =>
        page.items.map((item) => <div key={item.id}>{item.title}</div>),
      )}
      <button onClick={() => posts.fetchNextPage()}>Load more</button>
    </div>
  );
}

subscriptionOptions -- real-time data

import { useSubscription } from '@trpc/tanstack-react-query';
import { useTRPC } from '../utils/trpc';

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

  const sub = useSubscription(
    trpc.chat.onMessage.subscriptionOptions(
      { channelId: 'general' },
      {
        onData: (message) => {
          console.log('New message:', message);
        },
        onError: (err) => {
          console.error('Subscription error:', err);
        },
      },
    ),
  );

  return (
    <div>
      Status: {sub.status}, Last: {JSON.stringify(sub.data)}
    </div>
  );
}

Type inference

import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import type { inferInput, inferOutput } from '@trpc/tanstack-react-query';
import type { AppRouter } from '../server/router';

// Router-level inference
type Inputs = inferRouterInputs<AppRouter>;
type Outputs = inferRouterOutputs<AppRouter>;
type UserInput = Inputs['user']['byId'];
type UserOutput = Outputs['user']['byId'];

// Procedure-level inference (inside component)
// const trpc = useTRPC();
// type Input = inferInput<typeof trpc.user.byId>;
// type Output = inferOutput<typeof trpc.user.byId>;

Accessing the tRPC client directly

import { useTRPCClient } from '../utils/trpc';

async function DirectCall() {
  const client = useTRPCClient();
  const result = await client.user.byId.query({ id: '1' });
}

Common Mistakes

Using useQuery without the queryOptions factory

Calling useQuery({ queryKey: [...], queryFn: ... }) manually bypasses tRPC's type-safe query key generation and loses autocomplete. Always use the options factory.

// WRONG
useQuery({ queryKey: ['user', id], queryFn: () => fetch(...) });

// CORRECT
const trpc = useTRPC();
useQuery(trpc.user.byId.queryOptions({ id }));

Missing TRPCProvider wrapper

useTRPC() throws "can only be used inside a <TRPCProvider>" if the component tree is not wrapped. Ensure TRPCProvider is mounted above all components that call useTRPC(), typically in your root App component.

Invalidating queries with the classic utils pattern

The new @trpc/tanstack-react-query package does not use utils.invalidate(). Use queryClient.invalidateQueries() with queryFilter() instead.

// WRONG -- classic pattern does not work with new package
utils.post.invalidate();

// CORRECT
const trpc = useTRPC();
const queryClient = useQueryClient();
queryClient.invalidateQueries(trpc.post.queryFilter());

Creating a singleton QueryClient for SSR

In server-rendered apps, a singleton QueryClient leaks data between requests. Always create a new QueryClient per request on the server, and reuse a single instance only in the browser.

// WRONG
const queryClient = new QueryClient(); // shared across requests!

// CORRECT
function getQueryClient() {
  if (typeof window === 'undefined') return makeQueryClient();
  if (!browserQueryClient) browserQueryClient = makeQueryClient();
  return browserQueryClient;
}

See Also

  • [client-setup] -- vanilla tRPC client, links, and transport configuration
  • [links] -- httpBatchLink, splitLink, and other link types
  • [react-query-classic-migration] -- migrating from @trpc/react-query to @trpc/tanstack-react-query
  • [nextjs-app-router] -- using this integration with Next.js App Router and RSC

Files

1
9.2 KB

Agent reviews

0

No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.

More from trpc/trpc8

adapter-aws-lambda

Deploy tRPC on AWS Lambda with awsLambdaRequestHandler() from @trpc/server/adapters/aws-lambda for API Gateway v1 (REST, APIGatewayProxyEvent) and v2 (HTTP, APIGatewayProxyEventV2), and Lambda Function URLs. Enable response streaming with awsLambdaStreamingRequestHandler() wrapped in awslambda.strea

Scan passed 0
adapter-express

Mount tRPC as Express middleware with createExpressMiddleware() from @trpc/server/adapters/express. Access Express req/res in createContext via CreateExpressContextOptions. Mount at a path prefix like app.use('/trpc', ...). Avoid global express.json() conflicting with tRPC body parsing for FormData.

Scan passed 0
adapter-fastify

Mount tRPC as a Fastify plugin with fastifyTRPCPlugin from @trpc/server/adapters/fastify. Configure prefix, trpcOptions (router, createContext, onError). Enable WebSocket subscriptions with useWSS and @fastify/websocket. Set routerOptions.maxParamLength for batch requests. Requires Fastify v5+. Fast

Scan passed 0
adapter-fetch

Deploy tRPC on WinterCG-compliant edge runtimes with fetchRequestHandler() from @trpc/server/adapters/fetch. Supports Cloudflare Workers, Deno Deploy, Vercel Edge Runtime, Astro, Remix, SolidStart. FetchCreateContextFnOptions provides req (Request) and resHeaders (Headers) for context creation. The

Scan passed 0
adapter-standalone

Mount tRPC on Node.js built-in HTTP server with createHTTPServer() from @trpc/server/adapters/standalone, createHTTPHandler() for custom http.createServer, createHTTP2Handler() for HTTP/2 with TLS. Configure basePath to slice URL prefix, CORS via the cors npm package passed as middleware option. Cre

Scan passed 0
auth

Implement JWT/cookie authentication and authorization in tRPC using createContext for user extraction, t.middleware with opts.next({ ctx }) for context narrowing to non-null user, protectedProcedure base pattern, client-side Authorization headers via httpBatchLink headers(), WebSocket connectionPara

Scan passed 0
caching

Set HTTP cache headers on tRPC query responses via responseMeta callback for CDN and browser caching. Configure Cache-Control, s-maxage, stale-while-revalidate. Handle caching with batching and authenticated requests. Avoid caching mutations, errors, and authenticated responses.

Scan passed 0
client-setup

Create a vanilla tRPC client with createTRPCClient<AppRouter>(), configure link chain with httpBatchLink/httpLink, dynamic headers for auth, transformer on links (not client constructor). Infer types with inferRouterInputs and inferRouterOutputs. AbortController signal support. TRPCClientError typin

Scan passed 0

Related frontend skillsscan passed