はじめに
本記事では、React 用のフォームライブラリ「react-hook-form」と「zod」の使い方を解説します。react-hook-form と zod を使えば、最小限のコードでバリデーション付きフォームを実装できます。
本記事を読むことで、以下のことが学べます。
- react-hook-form の基本的な使い方
- zod を使った基本的なスキーマ定義とバリデーション
- refine を使った高度なバリデーション
- TypeScriptでの記述時の型のポイント
※ コードのみの説明にするため、Water.css による最低限のデザインになっています。
react-hook-form のみでシンプルなフォームを作る
まずは、名前とメールアドレスだけの簡単なフォームを作ってみましょう。react-hook-form では、useForm
フックを使ってフォームの状態を管理します。
import { useForm } from "react-hook-form";
export default function MyForm() {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" placeholder="名前" {...register("name")} />
<input type="email" placeholder="メールアドレス" {...register("email")} />
<button type="submit">送信</button>
</form>
);
}
ここでのポイントは以下の 3 つです。
-
useForm
フックを使ってフォームの状態を管理 -
register
関数でフォームのフールドを登録 -
handleSubmit
関数で送信時の処理を定義
useForm
はフォーム全体の状態を管理するためのフックで、バリデーションやエラーハンドリングなどの機能を提供します。
register
は、フォーム内の各フィールドをuseForm
に登録するための関数です。これにより、react-hook-form がフィールドの状態を追跡できるようになります。
handleSubmit
は、フォーム送信時に呼び出される関数を定義するためのユーティリティ関数です。ここでは、フォームのデータを受け取って処理するonSubmit
関数を渡しています。
以上が、react-hook-form を使った基本的なフォームの実装例です。
デフォルト値とリセット機能の追加
次に、メールアドレスのデフォルト値を設定し、リセットボタンを追加してみましょう。
import { useForm } from "react-hook-form";
export function MyForm(props) {
const { defaultValues } = props;
const { register, handleSubmit, reset } = useForm({
defaultValues,
});
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" placeholder="名前" {...register("name")} />
<input type="email" placeholder="メールアドレス" {...register("email")} />
<button type="submit">送信</button>
<button type="button" onClick={() => reset()}>
リセット
</button>
</form>
);
}
// MyFormのpropsにdefaultValuesを渡します
export default function App() {
return (
<MyForm
defaultValues={{
email: "test@example.com",
}}
/>
);
}
useForm
フックには、defaultValues
オプションを指定することで、各フィールドのデフォルト値を設定できます。ここでは、email
フィールドに初期値を与えています。
また、reset
関数を使うと、フォームを初期状態にリセットすることができます。ここではリセットボタンを押すと、defaultValues
で指定した値に戻ります。
zod によるバリデーションの追加
基本的にフォームには、ユーザ入力のバリデーションが必須です。react-hook-form では、resolver
オプションを使って、柔軟なバリデーションを行えます。
ここでは、zod というスキーマ定義ライブラリを使って、フィールドのバリデーションを追加します。
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
// zodを使って、フィールドのスキーマを定義します
const schema = z.object({
name: z.string().min(1, "名前は必須です"),
email: z.string().email("正しいメールアドレスを入力してください"),
age: z.number().min(18, "18歳以上である必要があります"),
});
export default function MyForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
// zodResolver関数を使って、バリデーション用のリゾルバを作成し、
// そのまま作成したリゾルバを渡します
resolver: zodResolver(schema),
});
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" placeholder="名前" {...register("name")} />
{errors.name && <span style={errorStyle}>{errors.name.message}</span>}
<input type="email" placeholder="メールアドレス" {...register("email")} />
{errors.email && <span style={errorStyle}>{errors.email.message}</span>}
<input
type="number"
placeholder="年齢"
{...register("age", { valueAsNumber: true })}
/>
{errors.age && <span style={errorStyle}>{errors.age.message}</span>}
<button type="submit">送信</button>
</form>
);
}
const errorStyle = {
color: "red",
};
zod を使ったバリデーションは以下のようなステップで行います。
- zod を使って、フィールドのスキーマを定義
-
zodResolver
関数を使って、バリデーション用のリゾルバを作成- リゾルバは、フォームの入力値をチェックするためのバリデーションロジックを定義する関数です。
zodResolver
関数は、zod で定義したスキーマを受け取り、それに対応するリゾルバ(バリデーション関数)を作成します
- リゾルバは、フォームの入力値をチェックするためのバリデーションロジックを定義する関数です。
-
useForm
フックのresolver
オプションに、作成したリゾルバを渡す
zod のスキーマ定義は非常に直感的で、必須チェックや型チェックなどを簡単に行えます。
また、バリデーションエラーが発生した場合は、formState.errors
オブジェクトからエラーメッセージを取得して表示できます。
refine を使用した複雑なバリデーション
refine を使用したもう少し複雑なバリデーションの例を見てみましょう。
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
const schema = z
.object({
password: z
.string()
.min(8, "パスワードは8文字以上で入力してください")
.regex(/^[a-zA-Z0-9]+$/, "パスワードは英数字のみで入力してください"),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: "パスワードが一致しません",
path: ["confirmPassword"],
});
export default function MyForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
});
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="password"
placeholder="パスワード"
{...register("password")}
/>
{errors.password && (
<span style={errorStyle}>{errors.password.message}</span>
)}
<input
type="password"
placeholder="パスワード(確認用)"
{...register("confirmPassword")}
/>
{errors.confirmPassword && (
<span style={errorStyle}>{errors.confirmPassword.message}</span>
)}
<button type="submit">送信</button>
</form>
);
}
const errorStyle = {
color: "red",
};
ここでは、password
とconfirmPassword
という 2 つのパスワード入力欄を設け、それらが一致することを検証しています。
zod のrefine
メソッドを使うと、複数のフィールドを跨いだバリデーションを行えます。この例では、password
とconfirmPassword
が等しいかどうかをチェックし、一致しない場合はエラーを設定しています。
TypeScript のサンプルコード
上述の zod のバリデーション付きフォームを TypeScript で書き直してみましょう。
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
// フィールドのスキーマを zod で定義
const schema = z.object({
name: z.string().min(1, "名前は必須です"),
email: z.string().email("正しいメールアドレス形式で入力してください"),
age: z.coerce.number().min(18, "18歳以上である必要があります"),
});
// フォームの入力値の型を上述のスキーマから作成
type FormValues = z.infer<typeof schema>;
export default function MyForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
});
const onSubmit = (data: FormValues) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" placeholder="名前" {...register("name")} />
{errors.name && <span>{errors.name.message}</span>}
<input type="email" placeholder="メールアドレス" {...register("email")} />
{errors.email && <span>{errors.email.message}</span>}
<input type="number" placeholder="年齢" {...register("age")} />
{errors.age && <span>{errors.age.message}</span>}
<button type="submit">送信</button>
</form>
);
}
まず、z.infer<typeof schema>
を使って、zod のスキーマから TypeScript の型を推論します。
この FormValues
型を useForm
に渡すことで、フォームの入力値の型を指定できます。
そして coerce
メソッドを使うことで、文字列型の入力値を数値型に変換します。
まとめ
react-hook-form は、シンプルで使いやすく、かつ柔軟性の高いフォームライブラリです。zod もシンプルで使いやすく、かつ柔軟性の高いバリデーションライブラリです。本記事では、一部の機能を紹介しましたが、react-hook-form と zod にはまだまだ多くの機能があります。
以下に公式ドキュメントを参照しておきます。
執筆時点の各種バージョン
- "react": "18.2.0",
- "react-dom": "18.2.0",
- "react-hook-form": "7.51.0",
- "zod": "3.22.4"
- "@hookform/resolvers": "3.3.4"
- "typescript": "5.2.2"