🙆‍♀️

React Hook Form + Zodで他の項目によってvalidationが変わる場合の対応

2023/02/28に公開2

とある項目のvalidationが他の項目の値によって変わる場合の対応をした時の備忘録。
React Hook Formを併用した場合に限らずだとは思います。

例えば選んだプランによっては、入力が必須となる項目を作りたい場合。
.refineで入力値dataを受け取れるので、その中で判定します。
falseを返すとエラーとなります。
pathで渡した項目のエラーとなりmessageが受け取れます。

const schema = z
  .object({
    plan: z.coerce.number(),
    point: z.coerce
      .number()
      .min(100, { message: "ポイントを入力してください" }),
  })
  .refine(
    (data) => {
      if (data.planId === 1) {
        return data.point !== null && data.point !== 0;
      }
      return true;
    },
    {
      message:
        "プランが「法人プラン」の場合、「ポイント」は必須です。",
      path: ["point"],
    }
  );

おまけ
z.coerce.number()はReact Hook Formを使った場合フォームから受け取った値がStringになっているので強制的にNumberにするものです。

Discussion

nap5nap5

相関のあるプロパティでのバリデーションをsuperRefineで表現してみました。

export enum PlanType {
  PERSONAL = 1,
  BUSINESS = 2,
}

const SomethingFormSchema = z
  .object({
    planId: z
      .custom<Number>()
      .refine(
        (value) => {
          return !isNullOrUndefined(value)
        },
        (value) => {
          return {
            message: '必須入力です',
          }
        }
      )
      .nullable()
      .transform((value) => {
        return Number(value)
      }),
    point: z
      .custom<Number>()
      .refine(
        (value) => {
          return !isNullOrUndefined(value)
        },
        (value) => {
          return {
            message: '必須入力です',
          }
        }
      )
      .nullable()
      .transform((value) => {
        return Number(value)
      }),
  })
  .superRefine(({ planId, point }, ctx) => {
    if (planId === PlanType.PERSONAL && point > 3000) {
      ctx.addIssue({
        path: ['point'],
        code: 'custom',
        message: '個人プランの場合、3000ポイント以下を選択してください',
      })
    }
    if (planId === PlanType.BUSINESS && point <= 3000) {
      ctx.addIssue({
        path: ['point'],
        code: 'custom',
        message: '法人プランの場合、3001ポイント以上を選択してください',
      })
    }
  })

export type SomethingForm = z.infer<typeof SomethingFormSchema>

https://codesandbox.io/p/sandbox/nervous-cloud-7nsket?file=%2FREADME.md

/coerceページがデモになります。

簡単ですが、以上です。

Koji KobayashiKoji Kobayashi

ありがとうございます。
superRefineなるほど、こういうのもあるんですね。

component構造とかも勉強になります。