👏

Zodのrefineを使って""を弾くと、型定義からも""が除外された

に公開

react hook formを触っていると、『初期値に""を使いたいけど、バリデーション時には弾きたい』
と思い、refineを使用したがタイトルの問題が発生した。

🔧 問題

例えば、次のようなカテゴリー選択スキーマがあって

const schema = z.object({
  category: z
    .union([
      z.enum(["A", "B", "C", "D"]),
      z.literal(""),
    ])
    .refine(val => val !== "", {
      message: "カテゴリーを選択してください",
    }),
});

このとき、得られる型が以下のようになった。

export type Schema = z.infer<typeof schema>
{
  category: "A" | "B" | "C" | "D"
}

なんと、 "" が型から消えてしまった!

🤔 なぜ?

Zodはrefineを使って「常にバリデーションで失敗する値」がある場合、
その値は“ありえない”とみなして型定義から除外してしまうっぽい?

🙈 解決策

元の型を保持してasで明示した。

import { z } from "zod";

const rawCategory = z.union([
  z.enum(["A", "B", "C", "D"]),
  z.literal(""),
]);

const categorySchema = rawCategory.refine((val) => val !== "", {
  message: "カテゴリーを選択してください",
}) as z.ZodType<z.infer<typeof rawCategory>>;

export const schema = z.object({
  category: categorySchema,
});

export type Schema = z.infer<typeof schema>;

🎉 結果

"" が型に残り、バリデーション時に"" は弾けた!

export type Schema = z.infer<typeof schema>
{
  category: "A" | "B" | "C" | "D" | ""
}

Discussion