✉️

【VeeValidate × Zod】refine/superRefineがうまく動かないときの解決策

2023/09/09に公開

VeeValidate × Zodでフォームを作成する際、例えば、メールアドレスを再度確認のために入力してもらう場面など、refine/superRefineを利用することがあると思われます。

例えば、こんな感じです。

画面イメージ

Validation発火時

コード

export const schema = toTypedSchema(
  z
    .object({
      name: z
        .string({
          required_error: errorMessage.name_empty,
        })
        .nonempty(errorMessage.name_empty), // 注:別の項目
      email: z
        .string({
          required_error: errorMessage.email_empty,
        })
        .nonempty(errorMessage.email_empty)
        .email(errorMessage.email_check),
      emailConfirm: z
        .string({
          required_error: errorMessage.email_empty,
        })
        .nonempty(errorMessage.email_empty)
        .email(errorMessage.email_check)
        
        // 注:この下にも他のValidation項目が続きます。

    })
    .superRefine(({ email, emailConfirm }, ctx) => { // 問題のメールの再確認
      if (email !== emailConfirm) {
        ctx.addIssue({
          path: ["emailConfirm"],
          code: "custom",
          message: errorMessage.email_confirm,
        });
      }
    }),
);

ところが、このメールのチェック、他のすべてのValidationが解決した後でないと、発火してくれません。

それは、VeeValidateの公式のZod Schema Validationに、以下のような記述がある通りです。

こちらが問題のissueです。
https://github.com/logaretm/vee-validate/issues/4338

これは困ったなあと思い、調べたところ、偉大な先人がいらっしゃいまして、解決策がありました。
Zodの公式のissueです。

https://github.com/colinhacks/zod/issues/479#issuecomment-1700612571

他にも多数の解決策がありましたが、Zodの場合、一番当てはめやすかったので、こちらの解決策を選んでみました。

先ほどのコード例でしたら、以下の通り書き直すと、他Validationと同様のタイミングで発火してくれます。

export const schema = toTypedSchema(
  z.object({
    form: z.object({
      name: z
        .string({
          required_error: errorMessage.name_empty,
        })
        .nonempty(errorMessage.name_empty),
	
      // 注:この下にも他のValidation項目が続きます。
      
    }),
    formMail: z // メール系のschemaを分離して解決します。
      .object({
        email: z
          .string({
            required_error: errorMessage.email_empty,
          })
          .nonempty(errorMessage.email_empty)
          .email(errorMessage.email_check),
        emailConfirm: z
          .string({
            required_error: errorMessage.email_empty,
          })
          .nonempty(errorMessage.email_empty)
          .email(errorMessage.email_check),
      })
      .refine(({ email, emailConfirm }) => email === emailConfirm, {
        path: ["emailConfirm"],
        message: errorMessage.email_confirm,
      }),
  }),
);

いやあ、本当にありがたや。ありがたや。

もし、他にも同じ事象でお困りの方がいらっしゃいましたら、何かのご参考になれば幸いです。

最後までお読み頂き、ありがとうございました。

Discussion