Skip to content
tRPC

Partners

Migrating from the classic React Client

Migrating from the classic React Client

On this page

There are a few approaches to migrate over, and this library is a significant departure from the classic client, so we're not expecting anybody to do it in one shot. But you will probably want to try a combination of...

Codemod migration

We're working on a codemod to help you migrate your existing codebase over to the new client. This is already available to try but we need your feedback and contributions to improve it. Codemods are very tricky to get right so we're looking for your help to make it as effective as possible.

Run our upgrade CLI:

npx @trpc/upgrade

When prompted, select the transforms Migrate Hooks to xxxOptions API and Migrate context provider setup.

The CLI refuses to run on a git working tree with uncommitted changes, unless you pass --force. After the transforms it installs @trpc/tanstack-react-query and uninstalls @trpc/react-query, so pass --skipTanstackQuery if you want to keep both while you migrate gradually. It doesn't cover every pattern (useQueries, for example), so typecheck afterwards and migrate whatever is left by hand, using the mappings below.

npx tsc --noEmit

Gradual migration

The new and classic clients are compatible with each other and can live together in the same application. This means you can start migrating by using the new client in new parts of your application, and gradually migrate over existing usage as you see fit. Most importantly, Query Keys are identical, which means you can use the new client and classic client together and still rely on TanStack Query's caching.

Migrating the provider

Install the new package, and create its hooks next to the classic ones

pnpm add @trpc/tanstack-react-query
npm install @trpc/tanstack-react-query
yarn add @trpc/tanstack-react-query
bun add @trpc/tanstack-react-query
utils/trpc.ts
import { function createTRPCReact<TRouter extends AnyRouter, TSSRContext = unknown>(opts?: CreateTRPCReactOptions<TRouter>): CreateTRPCReact<TRouter, TSSRContext>createTRPCReact } from '@trpc/react-query';
import { 
function createTRPCContext<TRouter extends AnyRouter, TFeatureFlags extends FeatureFlags = {
    keyPrefix: false;
}>(): CreateTRPCContextResult<TRouter, TFeatureFlags>
createTRPCContext
} from '@trpc/tanstack-react-query';
import type {
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
} from '../server/router';
// Classic, until nothing uses it export const
const trpc: CreateTRPCReactBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, unknown> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
trpc
=
createTRPCReact<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, unknown>(opts?: CreateTRPCReactOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>> | undefined): CreateTRPCReactBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>, unknown> & DecorateRouterRecord<...>
createTRPCReact
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
>();
export const {
const TRPCProvider: TRPCProviderType<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, {
    keyPrefix: false;
}>
TRPCProvider
,
const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, {
    keyPrefix: false;
}>
useTRPC
,
const useTRPCClient: () => TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>
useTRPCClient
} =
createTRPCContext<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, {
    keyPrefix: false;
}>(): CreateTRPCContextResult<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, {
    keyPrefix: false;
}>
createTRPCContext
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
>();

The classic trpc.Provider takes the tRPC client as client; TRPCProvider takes it as trpcClient. While both are mounted, give them the same QueryClient and tRPC client, so they share one cache. trpc.createClient() becomes createTRPCClient<AppRouter>() from @trpc/client.

components/App.tsx
import { class QueryClientQueryClient, const QueryClientProvider: ({ client, children }: QueryClientProviderProps) => React.JSX.ElementQueryClientProvider } from '@tanstack/react-query';
import { function createTRPCClient<TRouter extends AnyRouter>(opts: CreateTRPCClientOptions<TRouter>): TRPCClient<TRouter>createTRPCClient, function httpBatchLink<TRouter extends AnyRouter>(opts: HTTPBatchLinkOptions<TRouter["_def"]["_config"]["$types"]>): TRPCLink<TRouter>httpBatchLink } from '@trpc/client';
import { function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)useState } from 'react';
import type { 
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
} from '../server/router';
import {
const trpc: CreateTRPCReactBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, unknown> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
trpc
,
const TRPCProvider: TRPCProviderType<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, {
    keyPrefix: false;
}>
TRPCProvider
} from '../utils/trpc';
export function
function App(props: {
    children: React.ReactNode;
}): JSX.Element
App
(
props: {
    children: React.ReactNode;
}
props
: { children: React.ReactNodechildren: export namespace ReactReact.type React.ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal | Promise<AwaitedReactNode> | null | undefinedReactNode }) {
const [const queryClient: QueryClientqueryClient] = useState<QueryClient>(initialState: QueryClient | (() => QueryClient)): [QueryClient, React.Dispatch<React.SetStateAction<QueryClient>>] (+1 overload)useState(() => new new QueryClient(config?: QueryClientConfig): QueryClientQueryClient()); const [
const trpcClient: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>
trpcClient
] =
useState<TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>>(initialState: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>> | (() => TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>>)): [...] (+1 overload)
useState
(() =>
createTRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: CreateTRPCClientOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>): TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>>
createTRPCClient
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>> & DecorateCreateRouterOptions<{}>
AppRouter
>({
links: TRPCLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>[]
links
: [
httpBatchLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>(opts: HTTPBatchLinkOptions<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>): TRPCLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>
httpBatchLink
({ url: string | URLurl: '/api/trpc' })],
}), ); return ( <const QueryClientProvider: ({ client, children }: QueryClientProviderProps) => React.JSX.ElementQueryClientProvider client: QueryClientclient={const queryClient: QueryClientqueryClient}> <
const TRPCProvider: TRPCProviderType<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, {
    keyPrefix: false;
}>
TRPCProvider
trpcClient: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>
trpcClient
={
const trpcClient: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>
trpcClient
} queryClient: QueryClientqueryClient={const queryClient: QueryClientqueryClient}>
{/* Remove once nothing uses the classic hooks */} <
const trpc: CreateTRPCReactBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, unknown> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
trpc
.
type Provider: TRPCProvider<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, unknown>
Provider
TRPCProviderProps<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{}>>, unknown>.client: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>> | TRPCUntypedClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>
client
={
const trpcClient: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>>
trpcClient
} queryClient: QueryClientqueryClient={const queryClient: QueryClientqueryClient}>
{
props: {
    children: React.ReactNode;
}
props
.children: React.ReactNodechildren}
</
const trpc: CreateTRPCReactBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, unknown> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
trpc
.
type Provider: TRPCProvider<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, unknown>
Provider
>
</
const TRPCProvider: TRPCProviderType<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>, {
    keyPrefix: false;
}>
TRPCProvider
>
</const QueryClientProvider: ({ client, children }: QueryClientProviderProps) => React.JSX.ElementQueryClientProvider> ); }

Migrating Queries

A classic query would look like this

import { 
const trpc: {
    greeting: {
        useQuery: (input: {
            name: string;
        }) => UseQueryResult<`Hello ${string}`>;
    };
}
trpc
} from './trpc';
function function Users(): voidUsers() { const const greetingQuery: UseQueryResult<`Hello ${string}`>greetingQuery =
const trpc: {
    greeting: {
        useQuery: (input: {
            name: string;
        }) => UseQueryResult<`Hello ${string}`>;
    };
}
trpc
.
greeting: {
    useQuery: (input: {
        name: string;
    }) => UseQueryResult<`Hello ${string}`>;
}
greeting
.
useQuery: (input: {
    name: string;
}) => UseQueryResult<`Hello ${string}`>
useQuery
({ name: stringname: 'Jerry' });
// greetingQuery.data === 'Hello Jerry' }

and changes to

import { function useQuery<TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = readonly unknown[]>(options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): DefinedUseQueryResult<TData, TError> (+2 overloads)useQuery } from '@tanstack/react-query';
import { 
const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
useTRPC
} from './trpc';
function function Users(): voidUsers() { const
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
trpc
=
function useTRPC(): TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
useTRPC
();
const
const greetingQuery: UseQueryResult<`Hello ${string}`, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
greetingQuery
=
useQuery<`Hello ${string}`, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, `Hello ${string}`, TRPCQueryKeyWithoutPrefix>(options: UndefinedInitialDataOptions<`Hello ${string}`, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, `Hello ${string}`, TRPCQueryKeyWithoutPrefix>, queryClient?: QueryClient): UseQueryResult<`Hello ${string}`, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>> (+2 overloads)
useQuery
(
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
trpc
.
greeting: DecorateQueryProcedure<{
    input: {
        name: string;
    };
    output: `Hello ${string}`;
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}> & Record<string, never>
greeting
.
DecorateQueryProcedure<{ input: { name: string; }; output: `Hello ${string}`; transformer: false; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }>.queryOptions: TRPCQueryOptions
<`Hello ${string}`, `Hello ${string}`>(input: {
    name: string;
}, opts?: UnusedSkipTokenTRPCQueryOptionsIn<`Hello ${string}`, `Hello ${string}`, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    keyPrefix: false;
}> | undefined) => UnusedSkipTokenTRPCQueryOptionsOut<`Hello ${string}`, `Hello ${string}`, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    keyPrefix: false;
}> (+2 overloads)
queryOptions
({ name: stringname: 'Jerry' }));
// greetingQuery.data === 'Hello Jerry' }

Migrating Invalidations and other QueryClient usages

A classic invalidation pattern would look like this

import { 
const trpc: {
    useUtils: () => {
        greeting: {
            invalidate: (input: {
                name: string;
            }) => Promise<void>;
        };
    };
}
trpc
} from './trpc';
function function Users(): voidUsers() { const
const utils: {
    greeting: {
        invalidate: (input: {
            name: string;
        }) => Promise<void>;
    };
}
utils
=
const trpc: {
    useUtils: () => {
        greeting: {
            invalidate: (input: {
                name: string;
            }) => Promise<void>;
        };
    };
}
trpc
.
useUtils: () => {
    greeting: {
        invalidate: (input: {
            name: string;
        }) => Promise<void>;
    };
}
useUtils
();
async function function (local function) invalidateGreeting(): Promise<void>invalidateGreeting() { await
const utils: {
    greeting: {
        invalidate: (input: {
            name: string;
        }) => Promise<void>;
    };
}
utils
.
greeting: {
    invalidate: (input: {
        name: string;
    }) => Promise<void>;
}
greeting
.
invalidate: (input: {
    name: string;
}) => Promise<void>
invalidate
({ name: stringname: 'Jerry' });
} }

and changes to

import { const useQueryClient: (queryClient?: QueryClient) => QueryClientuseQueryClient } from '@tanstack/react-query';
import { 
const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
useTRPC
} from './trpc';
function function Users(): voidUsers() { const
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
trpc
=
function useTRPC(): TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
useTRPC
();
const const queryClient: QueryClientqueryClient = function useQueryClient(queryClient?: QueryClient): QueryClientuseQueryClient(); async function function (local function) invalidateGreeting(): Promise<void>invalidateGreeting() { await const queryClient: QueryClientqueryClient.
QueryClient.invalidateQueries<TRPCQueryKeyWithoutPrefix & {
    [dataTagSymbol]: `Hello ${string}`;
    [dataTagErrorSymbol]: TRPCClientErrorLike<{
        transformer: false;
        errorShape: DefaultErrorShape;
    }>;
}>(filters?: InvalidateQueryFilters<TRPCQueryKeyWithoutPrefix & {
    [dataTagSymbol]: `Hello ${string}`;
    [dataTagErrorSymbol]: TRPCClientErrorLike<{
        transformer: false;
        errorShape: DefaultErrorShape;
    }>;
}> | undefined, options?: InvalidateOptions): Promise<void>
invalidateQueries
(
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
trpc
.
greeting: DecorateQueryProcedure<{
    input: {
        name: string;
    };
    output: `Hello ${string}`;
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}> & Record<string, never>
greeting
.
DecorateQueryProcedure<{ input: { name: string; }; output: `Hello ${string}`; transformer: false; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }>.queryFilter: (input?: Partial<{
    name: string;
}> | undefined, filters?: QueryFilters<TRPCQueryKeyWithoutPrefix & {
    [dataTagSymbol]: `Hello ${string}`;
    [dataTagErrorSymbol]: TRPCClientErrorLike<{
        transformer: false;
        errorShape: DefaultErrorShape;
    }>;
}> | undefined) => WithRequired<QueryFilters<TRPCQueryKeyWithoutPrefix & {
    [dataTagSymbol]: `Hello ${string}`;
    [dataTagErrorSymbol]: TRPCClientErrorLike<...>;
}>, "queryKey">
queryFilter
({ name?: string | undefinedname: 'Jerry' }),
); } }

This is the same for any QueryClient usage, instead of using tRPC's useUtils you can now follow the TanStack documentation directly

Migrating Mutations

A classic mutation might look like this

import { 
const trpc: {
    createUser: {
        useMutation: () => UseMutationResult<string, unknown, {
            name: string;
        }>;
    };
}
trpc
} from './trpc';
function function Users(): voidUsers() { const
const createUserMutation: UseMutationResult<string, unknown, {
    name: string;
}>
createUserMutation
=
const trpc: {
    createUser: {
        useMutation: () => UseMutationResult<string, unknown, {
            name: string;
        }>;
    };
}
trpc
.
createUser: {
    useMutation: () => UseMutationResult<string, unknown, {
        name: string;
    }>;
}
createUser
.
useMutation: () => UseMutationResult<string, unknown, {
    name: string;
}>
useMutation
();
const createUserMutation: UseMutationResult<string, unknown, {
    name: string;
}>
createUserMutation
.
mutate: (variables: {
    name: string;
}, options?: MutateOptions<string, unknown, {
    name: string;
}, unknown> | undefined) => void
mutate
({ name: stringname: 'Jerry' });
}

and changes to

import { function useMutation<TData = unknown, TError = Error, TVariables = void, TOnMutateResult = unknown>(options: UseMutationOptions<TData, TError, TVariables, TOnMutateResult>, queryClient?: QueryClient): UseMutationResult<TData, TError, TVariables, TOnMutateResult>useMutation } from '@tanstack/react-query';
import { 
const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    createUser: MutationProcedure<{
        input: {
            name: string;
        };
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
useTRPC
} from './trpc';
function function Users(): voidUsers() { const
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    createUser: MutationProcedure<{
        input: {
            name: string;
        };
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
trpc
=
function useTRPC(): TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    createUser: MutationProcedure<{
        input: {
            name: string;
        };
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
useTRPC
();
const
const createUserMutation: UseMutationResult<string, TRPCClientErrorLike<{
    input: {
        name: string;
    };
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, {
    name: string;
}, undefined>
createUserMutation
=
useMutation<string, TRPCClientErrorLike<{
    input: {
        name: string;
    };
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, {
    name: string;
}, undefined>(options: UseMutationOptions<string, TRPCClientErrorLike<{
    input: {
        name: string;
    };
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, {
    name: string;
}, undefined>, queryClient?: QueryClient): UseMutationResult<...>
useMutation
(
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    createUser: MutationProcedure<{
        input: {
            name: string;
        };
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
trpc
.
createUser: DecorateMutationProcedure<{
    input: {
        name: string;
    };
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>
createUser
.
DecorateMutationProcedure<{ input: { name: string; }; output: string; transformer: false; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }>.mutationOptions: TRPCMutationOptions
<undefined>(opts?: TRPCMutationOptionsIn<{
    name: string;
}, TRPCClientErrorLike<{
    input: {
        name: string;
    };
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, string, undefined, {
    keyPrefix: false;
}> | undefined) => TRPCMutationOptionsOut<{
    name: string;
}, TRPCClientErrorLike<{
    input: {
        name: string;
    };
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, string, undefined, {
    keyPrefix: false;
}>
mutationOptions
());
const createUserMutation: UseMutationResult<string, TRPCClientErrorLike<{
    input: {
        name: string;
    };
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, {
    name: string;
}, undefined>
createUserMutation
.
mutate: (variables: {
    name: string;
}, options?: MutateOptions<string, TRPCClientErrorLike<{
    input: {
        name: string;
    };
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}>, {
    name: string;
}, undefined> | undefined) => void
mutate
({ name: stringname: 'Jerry' });
}

Equivalents for the other hooks and useUtils

Every hook takes the trpc from useTRPC(), and utils becomes const queryClient = useQueryClient(). Here a is a procedure and r a router.

ClassicNew
trpc.a.useSuspenseQuery(input)useSuspenseQuery(trpc.a.queryOptions(input)), which returns the query, not a [data, query] pair
trpc.a.useInfiniteQuery(input, opts)useInfiniteQuery(trpc.a.infiniteQueryOptions(input, opts))
trpc.a.useSubscription(input, opts)useSubscription(trpc.a.subscriptionOptions(input, opts)), imported from @trpc/tanstack-react-query
trpc.useQueries((t) => [t.a(input)])useQueries({ queries: [trpc.a.queryOptions(input)] })
utils.a.invalidate(input)queryClient.invalidateQueries(trpc.a.queryFilter(input))
utils.r.invalidate() / utils.invalidate()queryClient.invalidateQueries(trpc.r.pathFilter()) / (trpc.pathFilter())
utils.a.refetch(), reset(), cancel()refetchQueries, resetQueries, cancelQueries with the same filters
utils.a.fetch(input), prefetch, ensureDataqueryClient.fetchQuery(trpc.a.queryOptions(input)), prefetchQuery, ensureQueryData
utils.a.fetchInfinite(input), prefetchInfinitefetchInfiniteQuery(trpc.a.infiniteQueryOptions(input)), prefetchInfiniteQuery
utils.a.getData(input), setData(input, updater)queryClient.getQueryData(trpc.a.queryKey(input)), setQueryData(trpc.a.queryKey(input), updater)
utils.a.getInfiniteData, setInfiniteDataThe same, with trpc.a.infiniteQueryKey(input)
utils.clientuseTRPCClient()

Migrating abortOnUnmount

The classic integration takes abortOnUnmount when you create the client, which sets the default for every query — an individual query can still override it through its own trpc option

trpc.ts
import { function createTRPCReact<TRouter extends AnyRouter, TSSRContext = unknown>(opts?: CreateTRPCReactOptions<TRouter>): CreateTRPCReact<TRouter, TSSRContext>createTRPCReact } from '@trpc/react-query';
import type { 
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
} from './server/router';
export const
const trpc: CreateTRPCReactBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, unknown> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
trpc
=
createTRPCReact<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, unknown>(opts?: CreateTRPCReactOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>> | undefined): CreateTRPCReactBase<...> & DecorateRouterRecord<...>
createTRPCReact
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>> & DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>
AppRouter
>({
CreateTRPCReactOptions<BuiltRouter<{ ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }, DecorateCreateRouterOptions<{ greeting: QueryProcedure<{ input: { name: string; }; output: `Hello ${string}`; meta: object; errorShape: DefaultErrorShape; }>; }>>>.abortOnUnmount?: boolean | undefinedabortOnUnmount: true, });

createTRPCContext() takes no options, so there is no global equivalent. Set abortOnUnmount per query instead, in the same trpc option the classic integration uses per request

import { function useQuery<TQueryFnData = unknown, TError = Error, TData = TQueryFnData, TQueryKey extends QueryKey = readonly unknown[]>(options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): DefinedUseQueryResult<TData, TError> (+2 overloads)useQuery } from '@tanstack/react-query';
import { 
const useTRPC: () => TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
useTRPC
} from './trpc';
function function Users(): voidUsers() { const
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
trpc
=
function useTRPC(): TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
useTRPC
();
const
const greetingQuery: UseQueryResult<`Hello ${string}`, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
greetingQuery
=
useQuery<`Hello ${string}`, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, `Hello ${string}`, TRPCQueryKeyWithoutPrefix>(options: UndefinedInitialDataOptions<`Hello ${string}`, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, `Hello ${string}`, TRPCQueryKeyWithoutPrefix>, queryClient?: QueryClient): UseQueryResult<`Hello ${string}`, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>> (+2 overloads)
useQuery
(
const trpc: TRPCOptionsProxy<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    greeting: QueryProcedure<{
        input: {
            name: string;
        };
        output: `Hello ${string}`;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, {
    keyPrefix: false;
}>
trpc
.
greeting: DecorateQueryProcedure<{
    input: {
        name: string;
    };
    output: `Hello ${string}`;
    transformer: false;
    errorShape: DefaultErrorShape;
    featureFlags: {
        keyPrefix: false;
    };
}> & Record<string, never>
greeting
.
DecorateQueryProcedure<{ input: { name: string; }; output: `Hello ${string}`; transformer: false; errorShape: DefaultErrorShape; featureFlags: { keyPrefix: false; }; }>.queryOptions: TRPCQueryOptions
<`Hello ${string}`, `Hello ${string}`>(input: {
    name: string;
}, opts?: UnusedSkipTokenTRPCQueryOptionsIn<`Hello ${string}`, `Hello ${string}`, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    keyPrefix: false;
}> | undefined) => UnusedSkipTokenTRPCQueryOptionsOut<`Hello ${string}`, `Hello ${string}`, TRPCClientErrorLike<{
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    keyPrefix: false;
}> (+2 overloads)
queryOptions
({ name: stringname: 'Jerry' }, { TRPCQueryBaseOptions.trpc?: TRPCReactRequestOptions | undefinedtrpc: { TRPCReactRequestOptions.abortOnUnmount?: boolean | undefinedabortOnUnmount: true } }),
); }

Migrating overrides

createTRPCContext() doesn't take the classic overrides either. To run something after every mutation, such as invalidating the whole cache, use TanStack's own MutationCache callbacks

utils/query-client.ts
import { class MutationCacheMutationCache, class QueryClientQueryClient } from '@tanstack/react-query';

export const const queryClient: QueryClientqueryClient: class QueryClientQueryClient = new new QueryClient(config?: QueryClientConfig): QueryClientQueryClient({
  QueryClientConfig.mutationCache?: MutationCache | undefinedmutationCache: new new MutationCache(config?: MutationCacheConfig): MutationCacheMutationCache({
    MutationCacheConfig.onSuccess?: ((data: unknown, variables: unknown, onMutateResult: unknown, mutation: Mutation<unknown, unknown, unknown>, context: MutationFunctionContext) => Promise<unknown> | unknown) | undefinedonSuccess: () => const queryClient: QueryClientqueryClient.QueryClient.invalidateQueries<readonly unknown[]>(filters?: InvalidateQueryFilters<readonly unknown[]> | undefined, options?: InvalidateOptions): Promise<void>invalidateQueries(),
  }),
});

Common mistakes

  • Destructuring useSuspenseQuery. The classic hook returns [data, query]; TanStack's returns the query, so const [data] = ... becomes const { data } = ....
  • Mounting a second QueryClient. While both clients are in use, a separate QueryClient for each provider splits the cache, so an invalidation from one side doesn't refresh queries on the other. Share one.
  • Losing @trpc/react-query mid-migration. The upgrade CLI uninstalls it unless you pass --skipTanstackQuery.
  • Trusting the codemod blindly. It rewrites the common patterns only. Typecheck after it runs, and check custom wrappers around tRPC hooks by hand.