Skip to content
tRPC

React Query Integration (Classic)

React Query Integration

On this page

tRPC offers a first class integration with React. Under the hood this is simply a wrapper around the very popular @tanstack/react-query, so we recommend that you familiarize yourself with React Query, as their docs go into much greater depth on its usage.

If you are using Next.js we recommend using our integration with that instead.

❓ Do I have to use an integration?

No! The integration is fully optional. You can use @tanstack/react-query using just a vanilla tRPC client, although then you'll have to manually manage query keys and do not get the same level of DX as when using the integration package.

utils/trpc.ts
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 type { 
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        list: QueryProcedure<{
            input: void;
            output: {
                id: number;
                title: string;
            }[];
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>> & DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        list: QueryProcedure<{
            input: void;
            output: {
                id: number;
                title: string;
            }[];
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>
AppRouter
} from '../server';
export const
const trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        list: QueryProcedure<{
            input: void;
            output: {
                id: number;
                title: string;
            }[];
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>>
trpc
=
createTRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        list: QueryProcedure<{
            input: void;
            output: {
                id: number;
                title: string;
            }[];
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>>(opts: CreateTRPCClientOptions<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>>): TRPCClient<...>
createTRPCClient
<
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        list: QueryProcedure<{
            input: void;
            output: {
                id: number;
                title: string;
            }[];
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>> & DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        list: QueryProcedure<{
            input: void;
            output: {
                id: number;
                title: string;
            }[];
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>
AppRouter
>({
links: TRPCLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        list: QueryProcedure<{
            input: void;
            output: {
                id: number;
                title: string;
            }[];
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>>[]
links
: [
httpBatchLink<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        list: QueryProcedure<{
            input: void;
            output: {
                id: number;
                title: string;
            }[];
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>>(opts: HTTPBatchLinkOptions<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>): TRPCLink<...>
httpBatchLink
({ url: string | URLurl: 'YOUR_API_URL' })],
});
components/PostList.tsx
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 trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        list: QueryProcedure<{
            input: void;
            output: {
                id: number;
                title: string;
            }[];
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>>
trpc
} from '../utils/trpc';
function function PostList(): voidPostList() { const {
const data: {
    id: number;
    title: string;
}[] | undefined
data
} =
useQuery<{
    id: number;
    title: string;
}[], Error, {
    id: number;
    title: string;
}[], string[]>(options: UndefinedInitialDataOptions<{
    id: number;
    title: string;
}[], Error, {
    id: number;
    title: string;
}[], string[]>, queryClient?: QueryClient): UseQueryResult<{
    id: number;
    title: string;
}[], Error> (+2 overloads)
useQuery
({
queryKey: string[]queryKey: ['posts'],
queryFn?: unique symbol | QueryFunction<{
    id: number;
    title: string;
}[], string[], never> | undefined
queryFn
: () =>
const trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        list: QueryProcedure<{
            input: void;
            output: {
                id: number;
                title: string;
            }[];
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>>
trpc
.
post: DecoratedProcedureRecord<{
    transformer: false;
    errorShape: DefaultErrorShape;
}, DecorateCreateRouterOptions<{
    list: QueryProcedure<{
        input: void;
        output: {
            id: number;
            title: string;
        }[];
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
post
.
list: {
    query: Resolver<{
        input: void;
        output: {
            id: number;
            title: string;
        }[];
        errorShape: DefaultErrorShape;
        transformer: false;
    }>;
}
list
.
query: (input: void, opts?: TRPCProcedureOptions) => TRPCTaggedPromise<{
    id: number;
    title: string;
}[], DefaultErrorShape>
query
(),
});
const data: {
    id: number;
    title: string;
}[] | undefined
data
; // Post[]
// ... }

The tRPC React Query Integration

This library enables usage directly within React components.

pages/IndexPage.tsx
import { 
const trpc: CreateTRPCReactBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            name: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    goodbye: MutationProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, unknown> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>
trpc
} from '../utils/trpc';
export default function function IndexPage(): React.JSX.ElementIndexPage() { const
const helloQuery: UseTRPCQueryResult<{
    greeting: string;
}, TRPCClientErrorLike<{
    input: {
        name: string;
    };
    output: {
        greeting: string;
    };
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
helloQuery
=
const trpc: CreateTRPCReactBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            name: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    goodbye: MutationProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, unknown> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>
trpc
.
hello: DecoratedQuery<{
    input: {
        name: string;
    };
    output: {
        greeting: string;
    };
    transformer: false;
    errorShape: DefaultErrorShape;
}>
hello
.
useQuery: ProcedureUseQuery
<{
    greeting: string;
}, {
    greeting: string;
}>(input: {
    name: string;
} | unique symbol, opts?: UseTRPCQueryOptions<{
    greeting: string;
}, {
    greeting: string;
}, TRPCClientErrorLike<{
    input: {
        name: string;
    };
    output: {
        greeting: string;
    };
    transformer: false;
    errorShape: DefaultErrorShape;
}>, {
    greeting: string;
}> | undefined) => UseTRPCQueryResult<{
    greeting: string;
}, TRPCClientErrorLike<{
    input: {
        name: string;
    };
    output: {
        greeting: string;
    };
    transformer: false;
    errorShape: DefaultErrorShape;
}>> (+1 overload)
useQuery
({ name: stringname: 'Bob' });
const
const goodbyeMutation: UseTRPCMutationResult<string, TRPCClientErrorLike<{
    input: void;
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
}>, void, unknown>
goodbyeMutation
=
const trpc: CreateTRPCReactBase<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: {
            name: string;
        };
        output: {
            greeting: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    goodbye: MutationProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>, unknown> & DecorateRouterRecord<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>
trpc
.
goodbye: DecoratedMutation<{
    input: void;
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
}>
goodbye
.
useMutation: <unknown>(opts?: UseTRPCMutationOptions<void, TRPCClientErrorLike<{
    input: void;
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
}>, string, unknown> | undefined) => UseTRPCMutationResult<string, TRPCClientErrorLike<{
    input: void;
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
}>, void, unknown>
useMutation
();
return ( <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>{
const helloQuery: UseTRPCQueryResult<{
    greeting: string;
}, TRPCClientErrorLike<{
    input: {
        name: string;
    };
    output: {
        greeting: string;
    };
    transformer: false;
    errorShape: DefaultErrorShape;
}>>
helloQuery
.
data: {
    greeting: string;
} | undefined
data
?.greeting: string | undefinedgreeting}</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() =>
const goodbyeMutation: UseTRPCMutationResult<string, TRPCClientErrorLike<{
    input: void;
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
}>, void, unknown>
goodbyeMutation
.
mutate: (variables?: void | undefined, options?: MutateOptions<string, TRPCClientErrorLike<{
    input: void;
    output: string;
    transformer: false;
    errorShape: DefaultErrorShape;
}>, void, unknown> | undefined) => void
mutate
()}>Say Goodbye</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> ); }

Differences to vanilla React Query

The wrapper abstracts some aspects of React Query for you:

  • Query Keys - these are generated and managed by tRPC on your behalf, based on the procedure inputs you provide
    • If you need the query key which tRPC calculates, you can use getQueryKey
  • Type safe by default - the types you provide in your tRPC Backend also drive the types of your React Query client, providing safety throughout your React app

Pages under React Query Integration (Classic)