💎

React-Hook-Form + Zodでよくあるパスワードバリデーションを実装する

2023/01/15に公開
1

目標

パスワード設定画面によくある画面のバリデーションをReact-Hook-Form(以下 rfc), Zodで実装します
実装画面

実装する項目は以下の通りです

  • 現在のパスワードと新しいパスワードが一致しているか
  • 新しいパスワードと確認用のパスワードが一致しているか

https://zenn.dev/nyatinte/articles/1383a26bdb28bf
こちらの記事に、rfczodを利用した基本的なフォームの実装法を載せてあります!
導入についてはこちらをご覧ください

結論

.refineまたは.superRefineを使いましょう!

const schema = z
  .object({
    currentPassword: z.string().min(1, 'パスワードを入力してください'),
    newPassword: z
      .string()
      .min(8, 'パスワードは8文字以上で入力してください')
      .regex(
        /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,100}$/i,
        'パスワードは半角英数字混合で入力してください'
      ),
    newPasswordConfirm: z
      .string()
      .min(1, '確認用のパスワードを入力してください'),
  })
  .superRefine(({ currentPassword, newPassword, newPasswordConfirm }, ctx) => {
    if (newPassword !== newPasswordConfirm) {
      ctx.addIssue({
        path: ['newPasswordConfirm'],
        code: 'custom',
        message: 'パスワードが一致しません',
      });
    }
    if (currentPassword === newPassword) {
      ctx.addIssue({
        path: ['newPassword'],
        code: 'custom',
        message: '現在のパスワードと同じです',
      });
    }
  });

一度スキーマを作成し、メソッドの.superRefineで高度な処理を実装します。
rfc のwatch で値を取得してバリデーションという方法と比較して

  • バリデーションロジックを一箇所にまとめられる
  • 余計な変数を定義する必要がなくなる
    といったコードの可読性を上げることができます。

解説

https://github.com/colinhacks/zod#superRefine

  .superRefine(({ currentPassword, newPassword, newPasswordConfirm }, ctx) => {
    // 処理
  })

superRefineの中の関数にはval, ctxが渡されます。
valz.objectで定義したフィールドの値がオブジェクト形式で渡されるので、必要なものを分割代入で取得しています。

一方ctxの型は下記のようになっています

types.ts
export type RefinementCtx = {
  addIssue: (arg: IssueData) => void;
  path: (string | number)[];
};

addIssueという関数と、各フィールドへのパスの配列pathが含まれています。
このうち、addIssueを利用することにより、高度なバリデーションを実装します。

新しいパスワード と 確認用パスワード が一致しない場合
    if (newPassword !== newPasswordConfirm) {
      ctx.addIssue({
        path: ['newPasswordConfirm'],
        code: 'custom',
        message: 'パスワードが一致しません',
      })
    }
  • path:どのフィールドのバリデーションとして追加するか
  • code: エラーコード(custom 以外もある)
  • message: エラーメッセージ

他にも、バリデーションを早期中止するためのフラグfatalなどを引数で渡せますが、詳しくはドキュメントを参照してください

現在のパスワード と 新しいパスワード が一致した場合
    if (currentPassword === newPassword) {
      ctx.addIssue({
        path: ['newPassword'],
        code: 'custom',
        message: '現在のパスワードと同じです',
      })

あとはこれを rfc のリゾルバーに渡せば、いつものようにバリデーションが効きます

type FormValues = z.infer<typeof schema>
...

  const {
    register,
    ...
  } = useForm<FormValues>({
+    resolver: zodResolver(schema),
  })

終わりに

ここまで閲覧いただきありがとうございます!
zod の公式ドキュメントをあまり読んでいなかったので、こういった書き方があると知った時は感動しました。。
よければ ❤️ やコメントをしていただけると嬉しいです! (ご指摘のコメントもお待ちしております)

GitHubで編集を提案

Discussion

nap5nap5

コメントをしていただけると嬉しいです!

superRefine勉強になりました。記事書いてくださってありがとうございます。

記事記載の内容を少しアレンジしてデモを作成してみました。

https://codesandbox.io/p/sandbox/cranky-bassi-ctzzx5?file=%2Fsrc%2Ffeatures%2Fchange-password%2Fcomponents%2FForm.tsx&selection=[{"endColumn"%3A45%2C"endLineNumber"%3A20%2C"startColumn"%3A45%2C"startLineNumber"%3A20}]

簡単ですが、以上です。