はじめに
前回の記事では Zod 4 の変更点をまとめました。
今回はその Zod 4 と React Hook Form を組み合わせて、実際に「ログインフォーム」を作ってみます。
Zod とは
Zod とは、TypeScript 向けのスキーマ定義・バリデーションライブラリです。スキーマを定義すると、そのまま TypeScript の型にも反映できるのが大きな特徴です。
React Hook Form とは
React Hook Form は、React のフックをベースにしたフォーム管理ライブラリです。 公式の @hookform/resolvers を使うと、Zod といったバリデーションライブラリを簡単に組み合わせられます。
Zod と React Hook Form を組み合わせるメリット
- 型定義とバリデーションの一元化
- Zod のスキーマ定義だけで、型とバリデーションルールを同時に管理できる
- スキーマ通りのフォーム実装
- React Hook Form にスキーマ由来の型を渡すと、入力欄の名前や型がスキーマどおりに保たれる
- エディタのオートコンプリートが効き、存在しないフィールド指定でコンパイルエラーになる
- React Hook Form にスキーマ由来の型を渡すと、入力欄の名前や型がスキーマどおりに保たれる
- 安全なデータ受け渡し
- スキーマから推論した型を使うことで、フォーム間や API 呼び出し時に常にスキーマどおりのデータを扱える
TypeScript x Zod x React Hook Form でログインフォームを作る
今回は、メールアドレスとパスワード、確認用のパスワードを入力するログインフォームを用意します。
環境
- React: 18.2.0
- React Hook Form: 7.60.0
- @hookform/resolvers: 5.1.1
- 5.1.0 以降で、Zod 4 と連携が可能です
- https://github.com/react-hook-form/resolvers/releases/tag/v5.1.0
- Zod: 4.0.5
CodeSandbox の React(TS)テンプレートで動作確認しています。
スキーマを定義する
まずはスキーマを定義します。
import { z } from 'zod';
// 数字を含むことをチェックする正規表現
export const numberPattern = /(?=.*\d)/;
export const schema = z
.object({
// z.email() は、Zod 4 で導入された記法
email: z.email('正しいメールアドレスを入力してください'),
// パスワードは、16文字以上で数字を含むように
password: z
.string()
.min(16, '16文字以上で入力してください')
.regex(numberPattern, '数字を含めてください'),
confirm: z.string(),
})
// パスワードと確認用のものが一致しているかを確認する
.refine((data) => data.password === data.confirm, {
path: ['confirm'],
message: 'パスワードが一致しません',
});
// スキーマに沿った型を生成
export type FormData = z.infer<typeof schema>;
フォームを実装する
実際にフォームを実装してみます。ただ単にメールアドレスやパスワードを入力できるフォームだと味気なかったので、入力内容を watch して表示を変更できるようにしてみました。
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { numberPattern, schema, FormData } from './schema';
const App: React.FC = () => {
const {
watch,
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
// Zod で定義したスキーマをバリデーションに適用する
resolver: zodResolver(schema),
defaultValues: {
email: '',
password: '',
confirm: '',
},
});
// 入力内容によって表示を変更できるように、パスワードのフォームの内容を watch する
const password = watch('password');
// パスワードが 16 文字以上か
const isLongEnough = password.length >= 16;
// パスワードに数字を含んでいるかどうか
const hasNumber = numberPattern.test(password);
const onSubmit = (data: FormData) => {
// 今回は特に保存などしないので、console にデータを表示する
console.log('送信データ:', data);
};
return (
<div>
<h1>ログインフォーム</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>メールアドレス</label>
<input
type="email"
{...register('email')}
style={{ display: 'block' }}
/>
{errors.email && (
<p style={{ color: 'red' }}>{errors.email.message}</p>
)}
</div>
<div>
<label>パスワード</label>
<input
type="password"
{...register('password')}
style={{ display: 'block' }}
/>
<ul style={{ listStyle: 'none', padding: 0 }}>
{/* 入力内容によって表示を変更する */}
<li>{isLongEnough ? '✅' : '🚫'} 16文字以上</li>
<li>{hasNumber ? '✅' : '🚫'} 数字を含む</li>
</ul>
{errors.password && (
<p style={{ color: 'red' }}>{errors.password.message}</p>
)}
</div>
<div>
<label>パスワード(確認用)</label>
<input
type="password"
{...register('confirm')}
style={{ display: 'block' }}
/>
{errors.confirm && (
<p style={{ color: 'red' }}>{errors.confirm.message}</p>
)}
</div>
<button type="submit" style={{ marginTop: '12px' }}>
送信
</button>
</form>
</div>
);
};
export default App;
動作イメージ
さいごに
今回は Zod 4 と React Hook Form で基本的なログインフォームを作成しました。
次回は、もっと複雑なフォームやカスタムバリデータ、エラー表示のカスタマイズ例などについて掘り下げていきたいと思います。