react-hook-form + zod : バリデーションの実装
Zodは、TypeScript向けのスキーマベースのバリデーションライブラリである。
入力データやAPIレスポンスなどのデータ構造の検証を、TypeScriptの型と一貫して行うことが可能であり、react + TypeScriptでの開発を行う上では、導入している企業やプロジェクトも多い印象だ。
本投稿では、その使用方法について、残しておく。
インストール
npmパッケージを使用する例。
npm install react-hook-form@latest zod @hookform/resolvers
コード例 ①
解説は、コメントとして残す。
import { useState } from 'react';
import { useForm, SubmitHandler } from 'react-hook-form'
import { z } from "zod"
import { zodResolver } from "@hookform/resolvers/zod"
const MyForm = () => {
// パスワード
const [password, setPassword] = useState<string>("")
// zod スキーマ定義
const schema = z.object({
// バリデーションとして以下を設定
// 必須項目
// ○○○@◽️◽️.▲▲▲の形式であるかの確認
email: z.string().min(1, "メールアドレスを入力してください。").regex(/^[a-zA-Z0-9._]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,}$/, "○○○@◽️◽️.▲▲▲の形式で入力してください。"),
// バリデーションとして以下を設定
// 必須項目
// 半角英数字と記号(!?%_-+)が必ず含まれている かつ 8文字以上
password: z.string().min(1, "パスワードを入力してください。").regex(/^(?=.*[a-zA-Z])(?=.*\d)(?=.*[!?%_\-+])[a-zA-Z\d!?%_\-+]{8,}$/, "半角英数字と記号(!?%_-+)が必ず含まれている かつ 8文字以上である必要があります。"),
// バリデーションとして以下を設定
// 必須項目
// パスワードが一致しているか
confirmPassword: z.string().refine((value) => value === password, {message: "パスワードが一致しません。"})
})
// 型の設定
type FormValues = z.infer<typeof schema>
// register、handleSubmit、errorsなどの関数やオブジェクトを取得
// 型は、FormValueを設定
const {
register,
handleSubmit,
formState: {errors}
} = useForm<FormValues>({
// zodとreact-hook-formを紐づける
resolver: zodResolver(schema)
});
// 送信ボタンが押下された場合のイベント
// 型としてSubmitHandlerを設定
const onSubmit: SubmitHandler<FormValues> = (data) => {
console.log("submit", data)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>メールアドレス</label>
<input
type="text"
{...register("email")}
/>
{
errors.email && <p style={{ color: 'red', fontWeight: 'bold' }}>{ errors.email.message }</p>
}
</div>
<div>
<label>パスワード</label>
{/* バリデーションとして以下を設定 */}
{/* 必須項目 */}
{/* 半角英数字と記号(!?%_-+)が必ず含まれている かつ 8文字以上 */}
<input
type="password"
{...register("password")}
onChange={(e) => setPassword(e.target.value)}
/>
{
errors.password && <p style={{ color: 'red', fontWeight: 'bold' }}>{ errors.password.message }</p>
}
</div>
<div>
<label>パスワード再入力</label>
{/* バリデーションとして以下を設定 */}
{/* 必須項目 */}
{/* パスワードが一致しているか */}
<input
type="password"
{...register("confirmPassword")}
/>
{
errors.confirmPassword && <p style={{ color: 'red', fontWeight: 'bold' }}>{ errors.confirmPassword.message }</p>
}
</div>
<button type="submit">送信</button>
<button type="reset">リセット</button>
</form>
)
}
export default MyForm;
動作例
以下、動作例。
コード例 ②
チェックボックス・ラジオボタン・セレクトボックスなどの場合。
import { useForm, SubmitHandler } from "react-hook-form"
import { z } from "zod"
import { zodResolver } from "@hookform/resolvers/zod"
const schema = z.object({
//以下のバリデーションを設定
//いずれかのチェックボックスを選択しているか
//全て選択していないか(一つのみ選択可能)
cb: z.array(z.string()).min(1, "少なくとも一つ選択してください。").max(1, "いずれか一方のみ選択可能です。"),
rg: z.string(),
//以下のバリデーションを設定
//いずれかの要素が選択されているか(デフォルト値の選択してくださいもNG)
sb: z.string().min(1, "選択肢を選択してください")
})
type FormValues = z.infer<typeof schema>
export default function MyForm() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm<FormValues>({
defaultValues: {
cb: ["value1"], // チェックボックスの初期値を設定
rg: "value1" // ラジオボタンの初期値を設定
},
resolver: zodResolver(schema)
});
// 送信ボタンが押下された際のイベント
const onSubmit: SubmitHandler<FormValues> = (data) => {
console.log("submit", data)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>チェックボックス</label>
<div>
<label>
<input
type='checkbox'
value="value1"
{...register("cb")}
/>
value1
</label>
<label>
<input
type="checkbox"
value="value2"
{...register("cb")}
/>
value2
</label>
</div>
{errors.cb && (
<p style={{ color: "red", fontWeight: "bold" }}>{errors.cb.message}</p>
)}
</div>
<div>
<label>ラジオボタン</label>
<div>
<label>
<input
type='radio'
value='value1'
{...register("rg")}
/>
value1
</label>
<label>
<input
type='radio'
value='value2'
{...register("rg")}
/>
value2
</label>
</div>
</div>
<div>
<label>セレクトボックス</label>
<select
{...register("sb")}
>
<option value="">選択してください</option>
<option value="value1">Value1</option>
<option value="value2">Value2</option>
<option value="value3">Value3</option>
</select>
{
errors.sb && <p style={{ color: "red", fontWeight: "bold" }}>{ errors.sb.message }</p>
}
</div>
<button type='submit'>送信</button>
<button type="reset">リセット</button>
</form>
)
}