Skip to content
tRPC

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.

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 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' });
}