tRPCのinputでZodのバリデーションエラーを使ったときのエラーメッセージをいい感じにする
以下、tRPC v10 beta版の時点の検証内容です。
input()
のバリデーションにzodのスキーマを使った際、クライアント側でのエラーメッセージの表示で少し躓きました。
例えば、以下のようなtRPCのrouterを定義したとします。
t.procedure
.input(
z.object({
title: z.string().refine((val) => val.length <= 80, {
message: `タイトルは80字以内にしてください`,
})
})
)
.mutation(async ({ ctx: { session, req }, input }) => {
throw new TRPCError({
code: "BAD_REQUEST",
message: "エラーメッセージ",
});
}
問題はinput()
内でZodによるバリデーションエラーが発生したときと、TRPCError
を発生させたときでクライアント側でのエラーメッセージへのアクセスの仕方が異なることです。
t.procedure
.input(
// 👇 ① input()`内で発生したZodErrorの場合
z.object({
title: z.string().refine((val) => val.length <= 80, {
message: `タイトルは80字以内にしてください`,
})
})
)
.mutation(async ({ ctx: { session, req }, input }) => {
// 👇 ② TRPCErrorを発生させた場合
throw new TRPCError({
code: "BAD_REQUEST",
message: "エラーメッセージ",
});
}
クライアント側では以下のようにエラーメッセージにアクセスするとします。
const createPostMutation = trpcClient.post.create.useMutation();
// ...リクエスト部分は省略…
// こんな感じでエラーメッセージを表示
return <>
{createPostMutation.error && (
<p>{createPostMutation.error.message}</p>
)}
</>
すると「② TRPCErrorを発生させた場合」にはmessage
に指定した通りのエラーメッセージが表示されます。しかし「① input()
内で発生したZodErrorの場合」には以下の画像のようにオブジェクトの配列が文字列として表示されてしまいます。
解決策
trpcではAPI handlerを定義する部分でエラー発生時に呼び出されるonError()
メソッドを定義できます。
All errors that occur in a procedure go through the
onError
method before being sent to the client. Here you can handle or change errors.
ここでZodErrorの場合にはエラーオブジェクトをいじってやればクライアント側でcreatePostMutation.error.message
という呼び出し方をしたときにエラーメッセージだけを表示することもできそうです。
import { createNextApiHandler } from "@trpc/server/adapters/next";
import { ZodError } from "zod";
export default createNextApiHandler({
router: appRouter,
createContext: createTrpcContext,
onError({ error }) {
// `error.cause`がZodErrorのインスタンスの場合には`error.message`をZodのエラーメッセージに上書きする
if (error.cause instanceof ZodError) {
error.message = error.cause.issues[0].message;
}
},
});
これでinput()
内でZodによるバリデーションエラーが発生したときにもメッセージだけが表示されるようになりました。
UNAUTHORIZEDなどの英語でエラーメッセージが表示されてしまうのも、このonErrorにて書き換えできそうですね