Handle specific tRPC errors centrally

I would like to handle some errors of all of my queries centrally. e.g. I would like to have an 401 error always be handled the same way -> reroute the user to my login page. So I would like to prevent adding an onError func to every query I'm doing in my client. Is there a way to do that? I looked for a place to do it and found /server/trpc/trpc.ts where it sets up the protectedProcedure with the isAuthed function. Problem here is this isn't client side, so I can't call hooks including my router.
11 Replies
Leonidas
Leonidas•3y ago
Conceptually sounds like something that should be handled by middleware, does that exist for trpc?
Froxx
FroxxOP•3y ago
I mean the regular protectedProcedure for auth-protected routes does already use a middleware, so yeah it exists. But like I said this isn't client side, so no routing, etc. possible here
Leonidas
Leonidas•3y ago
Okay, i would define my own custom error then, a RedirectError with a path prop. The _app error fallback component should check if the error is an instanceof my RedirectError, and push the provided path clientside if this is the case With superjson you can define custom errors that can be shared from the client and the server, with custom props type safely!
CaptainStarz
CaptainStarz•3y ago
React Query Error Handling
After covering the sunshine cases of data fetching, it's time to look at situations where things don't go as planned and "Something went wrong..."
Leonidas
Leonidas•3y ago
That looks like a better place to check for the custom error, instead of in the _app error fallback componentđź‘Ť
Froxx
FroxxOP•3y ago
Where would I create / implement the QueryClient?
Leonidas
Leonidas•3y ago
Trpc is built on react query, their docs should reveal that
Leonidas
Leonidas•3y ago
Error Handling | tRPC
Whenever an error occurs in a procedure, tRPC responds to the client with an object that includes an "error" property. This property contains all the information that you need to handle the error in the client.
Froxx
FroxxOP•3y ago
Alright, I think I found the place to set the queryCache in the ct3a boilerplate. It's src/utils/trpc.ts where createTRPCNext is called. Still I got the problem that this has no access to hooks including useRouter since it's not in a component
Mugetsu
Mugetsu•3y ago
I do something like this.
// src/utils/trpc.ts
import superjson from "superjson";

import { httpBatchLink, loggerLink } from "@trpc/client";
import { createTRPCNext } from "@trpc/next";
import type { GetInferenceHelpers } from "@trpc/server";

import type { AppRouter } from "@/server/trpc/router/_app";
import { QueryCache } from "@tanstack/react-query";
import { signOut } from "next-auth/react";

const getBaseUrl = () => {
if (typeof window !== "undefined")
return process.env.NEXT_PUBLIC_APP_BASE_PATH; // browser should use relative url

if (process.env.VERCEL_URL)
return `https://${process.env.VERCEL_URL}${process.env.APP_BASE_PATH}`; // SSR should use vercel url

return `http://localhost:${process.env.PORT ?? 3000}${
process.env.APP_BASE_PATH
}`; // dev SSR should use localhost
};

export const trpc = createTRPCNext<AppRouter>({
config() {
return {
transformer: superjson,
links: [
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" && opts.result instanceof Error),
}),
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
queryClientConfig: {
defaultOptions: {
queries: {
retry: false,
refetchOnWindowFocus: false,
refetchOnMount: false,
},
},
queryCache: new QueryCache({
onError: async (error: any) => {
if (error.message === "UNAUTHORIZED") {
await signOut();
}
},
}),
},
};
},
ssr: false,
});

/**
* Inference helpers
* @example type HelloOutput = AppRouterTypes['example']['hello']['output']
**/
export type AppRouterTypes = GetInferenceHelpers<AppRouter>;
// src/utils/trpc.ts
import superjson from "superjson";

import { httpBatchLink, loggerLink } from "@trpc/client";
import { createTRPCNext } from "@trpc/next";
import type { GetInferenceHelpers } from "@trpc/server";

import type { AppRouter } from "@/server/trpc/router/_app";
import { QueryCache } from "@tanstack/react-query";
import { signOut } from "next-auth/react";

const getBaseUrl = () => {
if (typeof window !== "undefined")
return process.env.NEXT_PUBLIC_APP_BASE_PATH; // browser should use relative url

if (process.env.VERCEL_URL)
return `https://${process.env.VERCEL_URL}${process.env.APP_BASE_PATH}`; // SSR should use vercel url

return `http://localhost:${process.env.PORT ?? 3000}${
process.env.APP_BASE_PATH
}`; // dev SSR should use localhost
};

export const trpc = createTRPCNext<AppRouter>({
config() {
return {
transformer: superjson,
links: [
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" && opts.result instanceof Error),
}),
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
queryClientConfig: {
defaultOptions: {
queries: {
retry: false,
refetchOnWindowFocus: false,
refetchOnMount: false,
},
},
queryCache: new QueryCache({
onError: async (error: any) => {
if (error.message === "UNAUTHORIZED") {
await signOut();
}
},
}),
},
};
},
ssr: false,
});

/**
* Inference helpers
* @example type HelloOutput = AppRouterTypes['example']['hello']['output']
**/
export type AppRouterTypes = GetInferenceHelpers<AppRouter>;
Froxx
FroxxOP•3y ago
Huh. Just calling signOut might actually do the job as well. At least for this. I might run into situations where global client-side error-handling might come in handy, but I guess that's a problem for tomorrow-me. Cheers!
Want results from more Discord servers?
Add your server