ReactHookFormのdepsとzodで2つのフィールドのバイデーションを連携する
開発環境
- next 14.1.1
- react-hook-form 7.51.0
- zod 3.22.4
前提
ReactHookForm(以降RHF)とzodにまだ慣れていない、もしくはこれから利用する方を対象としています。
また、実装例はdepsに焦点を絞った簡易な実装です。
本題
使い所
フォームを実装しているときに、複数のフィールドがお互いの入力値によってバリデーションをかけたいことありますよね?
例えば、最小値と最大値を設定できるフォームでは最小値=2、最大値=1で登録できてしまうと困ります。
また、パスワードを更新するフォームでは、新しいパスワードと新しいパスワード(確認用)を設けることがあります。
こちらもまたサーバーへリクエストする前に一致していることをフロントエンドで検証しておきたいところです。
RHFとzodを利用すると簡単に実現できます。
実装例
今回はパスワード更新を例にします。
まず新しいパスワードと新しいパスワード(確認用)のフィールドを用意します。
// 新しいパスワード
<input
type="password"
{...register('newPassword', {
deps: 'newPasswordConfirm',
})}
/>;
// 新しいパスワードのエラーメッセージがあれば表示する
{
formState.errors.newPassword && (
<p style={{ color: 'red' }}>{formState.errors.newPassword.message}</p>
);
}
// 新しいパスワード(確認用)
<input
type="password"
{...register('newPasswordConfirm', {
deps: 'newPassword',
})}
/>;
// 新しいパスワード(確認用)のエラーメッセージがあれば表示する
{
formState.errors.newPasswordConfirm && (
<p style={{ color: 'red' }}>
{formState.errors.newPasswordConfirm.message}
</p>
);
}
ここで重要なのがdepsです。それぞれ互いのフィールド名を設定しています。
こうすることで、依存するフィールドの入力も検証のトリガーになります。
RHFではsubmit後に関しては入力があるたびに入力値に対する検証を行います。
もしdepsを設定していない場合、「新しいパスワードを正しく修正したのにエラーメッセージが消えない」という、不自然な挙動になる可能性があります。(この場合もsubmitすればバリデーションはパスしますが)
ユーザーに対して自然なフィードバックをするためにもdepsは設定してあげた方が良いです。
そしてzodのスキーマを定義して新しいパスワードと新しいパスワード(確認用)が一致しているか検証します。
const schema = z
.object({
newPassword: z
.string()
.min(8, { message: '8文字以上で入力してください' })
.max(32, { message: '32文字以内で入力してください' })
newPasswordConfirm: z
.string()
.min(8, { message: '8文字以上で入力してください' })
.max(32, { message: '32文字以内で入力してください' })
})
.refine(
({ newPassword, newPasswordConfirm }) => newPassword === newPasswordConfirm,
{
message: '新しいパスワードと新しいパスワード(確認用)が一致しません',
path: ['newPasswordConfirm'],
},
);
.refine()を利用することで任意のロジックでバリデーションをかけることができます。
今回は一致することを検証しています。
そして、pathで新しいパスワード(確認用)のエラーとして返しています。
さいごに
最近はSeverActionsも出てきていますが、CSRで RHF x zod の組み合わせでフォームを実装することはまだまだ多いと思いますので、ぜひ参考にしてください。