Run npx @trpc/upgrade for automated migration
Run the command `npx @trpc/upgrade` to automatically migrate from classic React Query hooks to the new options-factory API. When prompted, select 'Migrate Hooks to queryOptions/mutationOptions API' and 'Migrate context provider setup'. The codemod is a work in progress and handles common patterns, so always run TypeScript typecheck after it completes.
Install @trpc/tanstack-react-query package
Install the new package with `npm install @trpc/tanstack-react-query`. The classic `@trpc/react-query` package can remain installed during the migration period; both packages can coexist in the same application.
Migrate queries to useQuery with queryOptions
Replace tRPC's custom `useQuery` hook with TanStack React Query's standard `useQuery` hook. Old pattern: `const greeting = trpc.greeting.useQuery({ name: 'Jerry' });`. New pattern: call `useTRPC()` inside the component, then use `useQuery(trpc.greeting.queryOptions({ name: 'Jerry' }))`.
Migrate mutations to useMutation with mutationOptions
Replace tRPC's custom `useMutation` hook with TanStack React Query's standard `useMutation` hook. Old pattern: `const createUser = trpc.createUser.useMutation();`. New pattern: call `useTRPC()` inside the component, then use `useMutation(trpc.createUser.mutationOptions());` followed by `createUser.mutate({ name: 'Jerry' })`.
Replace utils.invalidate with queryClient.invalidateQueries
Replace classic tRPC `utils` invalidation with QueryClient methods. Old pattern: `await utils.greeting.invalidate({ name: 'Jerry' });`. New pattern: `const queryClient = useQueryClient(); const trpc = useTRPC(); await queryClient.invalidateQueries(trpc.greeting.queryFilter({ name: 'Jerry' }));`.
QueryClient operation mappings from classic utils
Map classic tRPC utils methods to QueryClient + trpc operations as follows: `utils.post.invalidate()` becomes `queryClient.invalidateQueries(trpc.post.queryFilter())`; `utils.post.refetch()` becomes `queryClient.refetchQueries(trpc.post.queryFilter())`; `utils.post.getData(input)` becomes `queryClient.getQueryData(trpc.post.byId.queryKey(input))`; `utils.post.setData(input,d)` becomes `queryClient.setQueryData(trpc.post.byId.queryKey(input),d)`.
Run TypeScript typecheck after migration
After migrating files, run `npx tsc --noEmit` to catch remaining type errors. Common issues include missing `useTRPC()` calls inside components, incorrect options shapes where `queryOptions` takes procedure input as first argument and `mutationOptions` takes TanStack Query options separately, and `useUtils()` references that need to become `useQueryClient()` plus `useTRPC()` pairs.
Uninstall classic package after full migration
Once all files are migrated and TypeScript typecheck passes, run `npm uninstall @trpc/react-query` to remove the classic package.
Codemod is incomplete; handle complex patterns manually
The `npx @trpc/upgrade` codemod is a work in progress and may miss complex cases such as dynamic query keys, conditional hooks, or custom wrappers around tRPC hooks. Always review the results and manually fix any remaining patterns.
Do not mix classic and new hooks in the same component
While the classic `@trpc/react-query` and new `@trpc/tanstack-react-query` packages can coexist in the same application, mixing their hooks in the same component creates confusing dual-provider requirements and makes code harder to reason about. Migrate one component at a time, converting all hooks from classic to new in a single pass.
TRPCProvider and QueryClientProvider wiring pattern
Wrap your app with QueryClientProvider first, then TRPCProvider. Create the QueryClient in a getQueryClient() function that returns a new QueryClient on the server and reuses a singleton instance in the browser. Pass both queryClient and trpcClient to TRPCProvider.
Setup TanStack React Query without React context for SPA/Vite
Create a singleton pattern using createTRPCOptionsProxy instead of createTRPCContext. Create a QueryClient singleton and trpcClient, then pass them to createTRPCOptionsProxy to export a trpc object. Wrap the app with QueryClientProvider only (no TRPCProvider), and import trpc directly instead of calling useTRPC().
Pitfall: Missing TRPCProvider causes useTRPC error
If useTRPC() is called outside a TRPCProvider-wrapped component tree, it throws an error saying 'can only be used inside a <TRPCProvider>'. Ensure TRPCProvider is mounted above all components that call useTRPC(), typically in the root App component.
Pitfall: Singleton QueryClient in SSR leaks data between requests
Creating a singleton QueryClient outside the browser context causes data to leak between server requests. Always create a new QueryClient per request on the server, and only reuse a singleton instance in the browser by checking typeof window === 'undefined'.
tRPC with React and TanStack Query
Integrate tRPC with React using TanStack Query by setting up useQuery, useMutation, and queryOptions. Refer to react-query-setup skill for full integration details.
Set up tRPC Provider for React Query integration
Wrap your application with trpc.Provider and QueryClientProvider. Create instances of QueryClient and trpc client, then pass them to the providers: <trpc.Provider client={trpcClient} queryClient={queryClient}><QueryClientProvider client={queryClient}>{/* Your app here */}</QueryClientProvider></trpc.Provider>