Inferring Types
It is often useful to wrap functionality of your @trpc/client
or @trpc/react-query
api within other functions. For this purpose, it's necessary to be able to infer input types and output types generated by your @trpc/server
router.
Inference Helpers
@trpc/server
exports the following helper types to assist with inferring these types from the AppRouter
exported by your @trpc/server
router:
inferRouterInputs<TRouter>
inferRouterOutputs<TRouter>
Let's assume we have this example router:
server.tsts
// @filename: server.tsimport {initTRPC } from '@trpc/server';import {z } from "zod";constt =initTRPC .create ();constappRouter =t .router ({post :t .router ({list :t .procedure .query (() => {// imaginary db callreturn [{id : 1,title : 'tRPC is the best!' }];}),byId :t .procedure .input (z .string ()).query (({input }) => {// imaginary db callreturn {id : 1,title : 'tRPC is the best!' };}),create :t .procedure .input (z .object ({title :z .string (),text :z .string (), })).mutation (({input }) => {// imaginary db callreturn {id : 1, ...input };}),}),});export typeAppRouter = typeofappRouter ;
server.tsts
// @filename: server.tsimport {initTRPC } from '@trpc/server';import {z } from "zod";constt =initTRPC .create ();constappRouter =t .router ({post :t .router ({list :t .procedure .query (() => {// imaginary db callreturn [{id : 1,title : 'tRPC is the best!' }];}),byId :t .procedure .input (z .string ()).query (({input }) => {// imaginary db callreturn {id : 1,title : 'tRPC is the best!' };}),create :t .procedure .input (z .object ({title :z .string (),text :z .string (), })).mutation (({input }) => {// imaginary db callreturn {id : 1, ...input };}),}),});export typeAppRouter = typeofappRouter ;
By traversing the router object, you can infer the types of the procedures. The following example shows how to infer the types of the procedures using the example appRouter
:
client.tsts
// @filename: client.tsimport type {inferRouterInputs ,inferRouterOutputs } from '@trpc/server';import type {AppRouter } from './server';typeRouterInput =inferRouterInputs <AppRouter >;typeRouterOutput =inferRouterOutputs <AppRouter >;typePostCreateInput =RouterInput ['post']['create'];typePostCreateOutput =RouterOutput ['post']['create'];
client.tsts
// @filename: client.tsimport type {inferRouterInputs ,inferRouterOutputs } from '@trpc/server';import type {AppRouter } from './server';typeRouterInput =inferRouterInputs <AppRouter >;typeRouterOutput =inferRouterOutputs <AppRouter >;typePostCreateInput =RouterInput ['post']['create'];typePostCreateOutput =RouterOutput ['post']['create'];
Infer TRPClientError
s based on your router
client.tsts
// @filename: client.tsimport {TRPCClientError } from '@trpc/client';import type {AppRouter } from './server';import {trpc } from './trpc';export functionisTRPCClientError (cause : unknown,):cause isTRPCClientError <AppRouter > {returncause instanceofTRPCClientError ;}async functionmain () {try {awaittrpc .post .byId .query ('1');} catch (cause ) {if (isTRPCClientError (cause )) {// `cause` is now typed as your router's `TRPCClientError`console .log ('data',cause .data );} else {// [...]}}}main ();
client.tsts
// @filename: client.tsimport {TRPCClientError } from '@trpc/client';import type {AppRouter } from './server';import {trpc } from './trpc';export functionisTRPCClientError (cause : unknown,):cause isTRPCClientError <AppRouter > {returncause instanceofTRPCClientError ;}async functionmain () {try {awaittrpc .post .byId .query ('1');} catch (cause ) {if (isTRPCClientError (cause )) {// `cause` is now typed as your router's `TRPCClientError`console .log ('data',cause .data );} else {// [...]}}}main ();
Infer React Query options based on your router
When creating custom hooks around tRPC procedures, it's sometimes necessary to have the types of the options inferred from the router. You can do so via the inferReactQueryProcedureOptions
helper exported from @trpc/react-query
.
ts
// @filename: trpc.tsimport {typeinferReactQueryProcedureOptions ,createTRPCReact } from '@trpc/react-query';import type {inferRouterInputs } from '@trpc/server';import type {AppRouter } from './server';export typeReactQueryOptions =inferReactQueryProcedureOptions <AppRouter >;export typeRouterInputs =inferRouterInputs <AppRouter >;export consttrpc =createTRPCReact <AppRouter >();// @filename: usePostCreate.tsimport { typeReactQueryOptions ,trpc } from './trpc';typePostCreateOptions =ReactQueryOptions ['post']['create'];functionusePostCreate (options ?:PostCreateOptions ) {constutils =trpc .useContext ();returntrpc .post .create .useMutation ({...options ,onSuccess (post ) {// invalidate all queries on the post router// when a new post is createdutils .post .invalidate ();options ?.onSuccess ?.(post );},});}// @filename: usePostById.tsimport {ReactQueryOptions ,RouterInputs ,trpc } from './trpc';typePostByIdOptions =ReactQueryOptions ['post']['byId'];typePostByIdInput =RouterInputs ['post']['byId'];functionusePostById (input :PostByIdInput ,options ?:PostByIdOptions ) {returntrpc .post .byId .useQuery (input ,options );}
ts
// @filename: trpc.tsimport {typeinferReactQueryProcedureOptions ,createTRPCReact } from '@trpc/react-query';import type {inferRouterInputs } from '@trpc/server';import type {AppRouter } from './server';export typeReactQueryOptions =inferReactQueryProcedureOptions <AppRouter >;export typeRouterInputs =inferRouterInputs <AppRouter >;export consttrpc =createTRPCReact <AppRouter >();// @filename: usePostCreate.tsimport { typeReactQueryOptions ,trpc } from './trpc';typePostCreateOptions =ReactQueryOptions ['post']['create'];functionusePostCreate (options ?:PostCreateOptions ) {constutils =trpc .useContext ();returntrpc .post .create .useMutation ({...options ,onSuccess (post ) {// invalidate all queries on the post router// when a new post is createdutils .post .invalidate ();options ?.onSuccess ?.(post );},});}// @filename: usePostById.tsimport {ReactQueryOptions ,RouterInputs ,trpc } from './trpc';typePostByIdOptions =ReactQueryOptions ['post']['byId'];typePostByIdInput =RouterInputs ['post']['byId'];functionusePostById (input :PostByIdInput ,options ?:PostByIdOptions ) {returntrpc .post .byId .useQuery (input ,options );}
You can also infer abstract types for router interfaces which you share around an application via a router factory. For example:
tsx
// @filename: factory.tsimport {t ,publicProcedure } from './trpc';// @trpc/react-query/shared exports several **Like types which can be used to generate abstract typesimport {RouterLike ,UtilsLike } from '@trpc/react-query/shared';// Factory function written by you, however you need,// so long as you can infer the resulting type of t.router() laterexport functioncreateMyRouter () {returnt .router ({createThing :publicProcedure .input (ThingRequest ).output (Thing ).mutation (/* do work */),listThings :publicProcedure .input (ThingQuery ).output (ThingArray ).query (/* do work */),})}// Infer the type of your router, and then generate the abstract types for use in the clienttypeMyRouterType =ReturnType <typeofcreateRouter >exportMyRouterLike =RouterLike <MyRouterType >exportMyRouterUtilsLike =UtilsLike <MyRouterType >// @filename: server.tsexport type= typeof AppRouter appRouter // Export your MyRouter types to the clientexport type {, MyRouterLike } from './factory' MyRouterUtilsLike // @filename: usePostCreate.tsximport type {trpc ,useContext ,MyRouterLike ,MyRouterUtilsLike } from './trpc';typeMyGenericComponentProps = {route :MyRouterLike utils :MyRouterUtilsLike }functionMyGenericComponent ({route ,utils }:MyGenericComponentProps ) {constthing =route .listThings .useQuery ({filter : "qwerty"})constmutation =route .doThing .useMutation ({onSuccess () {utils .listThings .invalidate ()}})functionhandleClick () {mutation .mutate ({name : "Thing 1"})}return /* ui */}functionMyPageComponent () {constutils =useContext ()return (<MyGenericComponent route ={trpc .deep .route .things }utils ={utils .deep .route .things }/>)}functionMyOtherPageComponent () {constutils =useContext ()return (<MyGenericComponent route ={trpc .different .things }utils ={utils .different .things }/>)}
tsx
// @filename: factory.tsimport {t ,publicProcedure } from './trpc';// @trpc/react-query/shared exports several **Like types which can be used to generate abstract typesimport {RouterLike ,UtilsLike } from '@trpc/react-query/shared';// Factory function written by you, however you need,// so long as you can infer the resulting type of t.router() laterexport functioncreateMyRouter () {returnt .router ({createThing :publicProcedure .input (ThingRequest ).output (Thing ).mutation (/* do work */),listThings :publicProcedure .input (ThingQuery ).output (ThingArray ).query (/* do work */),})}// Infer the type of your router, and then generate the abstract types for use in the clienttypeMyRouterType =ReturnType <typeofcreateRouter >exportMyRouterLike =RouterLike <MyRouterType >exportMyRouterUtilsLike =UtilsLike <MyRouterType >// @filename: server.tsexport type= typeof AppRouter appRouter // Export your MyRouter types to the clientexport type {} from './factory' MyRouterLike ,MyRouterUtilsLike // @filename: usePostCreate.tsximport type {trpc ,useContext ,MyRouterLike ,MyRouterUtilsLike } from './trpc';typeMyGenericComponentProps = {route :MyRouterLike utils :MyRouterUtilsLike }functionMyGenericComponent ({route ,utils }:MyGenericComponentProps ) {constthing =route .listThings .useQuery ({filter : "qwerty"})constmutation =route .doThing .useMutation ({onSuccess () {utils .listThings .invalidate ()}})functionhandleClick () {mutation .mutate ({name : "Thing 1"})}return /* ui */}functionMyPageComponent () {constutils =useContext ()return (<MyGenericComponent route ={trpc .deep .route .things }utils ={utils .deep .route .things }/>)}functionMyOtherPageComponent () {constutils =useContext ()return (<MyGenericComponent route ={trpc .different .things }utils ={utils .different .things }/>)}
A more complete working example can be found here