T
tRPC

❓-help

Vanilla Client Error Handling

Jjustindgm11/28/2023
What is the right way to handle errors when using the vanilla client? If I setup a client like so:
import type { Router } from '../../../server/src/routers'

export const trpcClient = createTRPCProxyClient<Router>({
links: [
httpBatchLink({
url: `${env.SERVER_URI}/trpc`
})
]
})
import type { Router } from '../../../server/src/routers'

export const trpcClient = createTRPCProxyClient<Router>({
links: [
httpBatchLink({
url: `${env.SERVER_URI}/trpc`
})
]
})
And execute a mutation like so:
const user = await trpcClient.user.create.mutate({ email: string; password: string })
const user = await trpcClient.user.create.mutate({ email: string; password: string })
How should I handle errors? If I try to directly catch when calling the mutate function, the error is typed as any
const user = await trpcClient.user.create.mutate({ email: string; password: string }).catch((error) => {
// error is any
}
const user = await trpcClient.user.create.mutate({ email: string; password: string }).catch((error) => {
// error is any
}
Is there a way to type narrow the error at runtime? On the server side, I am using the errorFormatter to add a custom field to my errors:
const t = initTRPC.context<Context>().create({
isDev: env.NODE_ENV !== 'production',
errorFormatter: ({ shape, error }) => {
return {
...shape,
data: {
...shape.data,
customError: error.cause instanceof CustomError ? error.cause.customCode : null
}
}
}
})
const t = initTRPC.context<Context>().create({
isDev: env.NODE_ENV !== 'production',
errorFormatter: ({ shape, error }) => {
return {
...shape,
data: {
...shape.data,
customError: error.cause instanceof CustomError ? error.cause.customCode : null
}
}
}
})
My current approach will be to use a runtime validation library like zod to check if this custom field exists on the error
const user = await trpcClient.user.create.mutate({ email: string; password: string }).catch((error) => {
const parsedError = errorSchema.safeParse(error)
if (parsedError.isSuccess) {
console.error(parsedError.data.data.customError)
} else {
console.error(error)
}
}
const user = await trpcClient.user.create.mutate({ email: string; password: string }).catch((error) => {
const parsedError = errorSchema.safeParse(error)
if (parsedError.isSuccess) {
console.error(parsedError.data.data.customError)
} else {
console.error(error)
}
}
But this is clearly not ideal. Is this the right/best way to approach error handling with the vanilla client, or is there a better approach?
Solution:
There is actually a docs page on this, does that answer the question? https://trpc.io/docs/client/vanilla/infer-types#infer-trpcclienterror-types
Jump to solution
KKrishna11/28/2023
I would also like to understand this... I also want to handle error centrally... for example if backend gives an unauthorised error (maybe due to token expiry)... Need to logout the user from frontend & bring the user back to their existing state once they login....
Jjustindgm11/28/2023
@Krishna Agreed, global handling of certain errors (especially authentication/token expiry), is exactly what my next question would be once I understand how to best handle errors in the most basic case.
Solution
Nnlucas11/30/2023
There is actually a docs page on this, does that answer the question? https://trpc.io/docs/client/vanilla/infer-types#infer-trpcclienterror-types
Inferring Types | tRPC
It is often useful to access the types of your API within your clients. For this purpose, you are able to infer the types contained in your AppRouter.
LLittleLily12/2/2023
also interested in knowing what the best solution for globally handling errors when using createTRPCProxyClient is. I found this discussion https://github.com/trpc/trpc/discussions/2036 but none of the solutions there seem to really be what I want. I'd like to be able to return custom responses to the client like 302 redirects on auth failures and such, but it seems like only createTRPCNext really supports that with its responseMeta field.
Jjustindgm12/2/2023
This is perfect! Thank you
BBillyBob1/16/2024
@Krishna @justindgm How do you handle errors centrally ?
import { redirect } from 'next/navigation'

export const customLink: TRPCLink<AppRouter> = () => {
return ({ next, op }) => {
return observable((observer) => {
const unsubscribe = next(op).subscribe({
next(value) {
observer.next(value)
},
error(err) {
observer.error(err)
if (err?.data?.code === 'UNAUTHORIZED') {
redirect('/login')
}
},
complete() {
observer.complete()
},
})
return unsubscribe
})
}
}

export const api = createTRPCProxyClient<AppRouter>({
transformer: superjson,
links: [
// loggerLink({
// enabled: (op) => false,
// // enabled: (op) =>
// // process.env.NODE_ENV === 'development' ||
// // (op.direction === 'down' && op.result instanceof Error),
// }),
customLink,
httpBatchLink({
url: 'http://localhost:4000/trpc',
headers: Object.fromEntries(headers()),
fetch(url, options) {
return fetch(url, {
...options,
credentials: 'include',
})
},
}),
],
})
import { redirect } from 'next/navigation'

export const customLink: TRPCLink<AppRouter> = () => {
return ({ next, op }) => {
return observable((observer) => {
const unsubscribe = next(op).subscribe({
next(value) {
observer.next(value)
},
error(err) {
observer.error(err)
if (err?.data?.code === 'UNAUTHORIZED') {
redirect('/login')
}
},
complete() {
observer.complete()
},
})
return unsubscribe
})
}
}

export const api = createTRPCProxyClient<AppRouter>({
transformer: superjson,
links: [
// loggerLink({
// enabled: (op) => false,
// // enabled: (op) =>
// // process.env.NODE_ENV === 'development' ||
// // (op.direction === 'down' && op.result instanceof Error),
// }),
customLink,
httpBatchLink({
url: 'http://localhost:4000/trpc',
headers: Object.fromEntries(headers()),
fetch(url, options) {
return fetch(url, {
...options,
credentials: 'include',
})
},
}),
],
})
Ive been trying this but the redirectdoes not seem to run

Looking for more? Join the community!

Recommended Posts
sidebar searchPlease how will I implement a search on the left sidebar to let the items on the search bar be searcWS with TRPC```ts │  Type '({ req, res, }: { req: Request; res: Response; }) => CreateInnerContextOpts' is tRPC is butchering object types in return typesSo if I have a mutation that returns this type ```ts { name?: string } ``` the type that tRPC infersWhy my Authorization does not update ?i have this in `_app.tsx` ```export default function App({ Component, pageProps }: AppProps) { retTypeError: queryClient.getMutationDefaults is not a function (it is undefined)Hi! There's not much code to show here, as I'm really not sure where the error actually occurs. InuseQuery never returning or hitting APII have something off in my configuration, i copied most of the files from the `❯ npm create t3-app@lType 'QueryClient' is missing the following properties from type 'QueryClient': queryCache, mutationI am trying to setup a new project and doing my first client side query and getting this error: ```trpc mutation call stuckI have an issue with a simple mutation procedure: `getPublicUser: publicProcedure .input(z.objeCreate client based on OpenAPIHi! I am running a golang server with an openapi spec and would love to use trpc client in my fronteTRPC with react query, getting error twice?When i am try to show TRPC error in the client side with react-query, i am getting error twice from Error building create-t3-app: Page couldn't be rendered statically because it used `cookies`I get the following error when building create-t3-app: ``` TRPCClientError: Dynamic server usage: PaCompressing parts of query/mutation inputHi, I'm using trpc with the proxyclient for typesafety but recently hit an obstacle where some strin`useSuspenseQuery` still runs a fetch on SSR even when setting `ssr: false` in the api configBeside double-fetching, this causes issues during rendering if you have auth on your routes as the SUnsubscription callback triggering immediatelyHey! I am currently using Bun that's queried via a Vue application. For some reason the unsubscribeMultiple optimistic updates and old data from refetchesHi all, I'm new to tRPC and React Query and I'm just trying to get my head around what exactly I'm dPass headers when prefetching using helpersI dont see a way to pass headers and cookies with either fetch/prefetch methods from the ssr helper.I am getting a errors after starting a TRPC project with T3. "Unsafe return of an `any` typed value"It seems like something is off between Prisma and TRPC but I can't figure out why the type infrence Is there a similar handler to createNextApiHandler for fastify?I'm trying to create a global error handler in fatsify but I can't find anything. Is this even a thiHow does trpc typing workI'm curious to know how trpc generates type without a code gen step , i am trying to acheive somethtRPC with app routerHi, where I can find basic and simple code for trpc for next.js app router?