Closed2

tRPCのinputでZodのバリデーションエラーを使ったときのエラーメッセージをいい感じにする

catnosecatnose

以下、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の場合」には以下の画像のようにオブジェクトの配列が文字列として表示されてしまいます。

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によるバリデーションエラーが発生したときにもメッセージだけが表示されるようになりました。

Zodのエラーメッセージだけが表示される

MelodyclueMelodyclue

UNAUTHORIZEDなどの英語でエラーメッセージが表示されてしまうのも、このonErrorにて書き換えできそうですね

このスクラップは2022/10/04にクローズされました