react-hook-form-zod は何をしますか?
React Hook Form v7 と Zod v4 を使用して、型安全でバリデーション付きのフォームを構築します。単一のスキーマがクライアントとサーバーの両方で動作し、z.infer による完全な TypeScript 推論を提供します。フォーム、マルチステップウィザードの構築、または制御されていない警告、リゾルバエラー、useFieldArray の問題、大規模フォームのパフォーマンス問題の修正に使用します。
インストール
npm install react-hook-form @hookform/resolvers zod
基本的な使用方法
1. Zod スキーマを定義する
import { z } from 'zod';
export const userSchema = z.object({
name: z.string().min(2, '名前は2文字以上必要です'),
email: z.string().email('有効なメールアドレスを入力してください'),
age: z.coerce.number().int().min(18, '18歳以上である必要があります'),
});
2. React Hook Form で使用する
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const formSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
type FormData = z.infer<typeof formSchema>;
export default function MyForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
resolver: zodResolver(formSchema),
});
const onSubmit = (data: FormData) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
{errors.name && <p>{errors.name.message}</p>}
<input {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}
<button type="submit">送信</button>
</form>
);
}
詳細な使用法
フィールドタイプ
- テキスト入力:
z.string().min().max().email() - 数値入力:
z.coerce.number().int().min().max() - セレクト:
z.enum()またはz.string()に.refine()を組み合わせる - チェックボックス:
z.boolean() - 配列:
z.array()をuseFieldArrayと一緒に使用
スキーマの共有
// shared/schema.ts
export const userSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
// client/Form.tsx
import { userSchema } from '../shared/schema';
// server/api.ts
import { userSchema } from '../shared/schema';
app.post('/api/users', async (req, res) => {
const result = userSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json(result.error);
}
// データベースに保存...
});
マルチステップフォーム(ウィザード)
const step1Schema = z.object({ name: z.string().min(2) });
const step2Schema = z.object({ email: z.string().email() });
const step3Schema = z.object({ age: z.coerce.number().int().min(18) });
// 各ステップは独自の useForm インスタンスを持ち、完了時にデータを集約します
useFieldArray による配列入力
import { useFieldArray } from 'react-hook-form';
const formSchema = z.object({
items: z.array(z.object({
name: z.string().min(1),
quantity: z.coerce.number().int().min(1),
})).min(1, '少なくとも1つのアイテムが必要です'),
});
type FormData = z.infer<typeof formSchema>;
function ItemsForm() {
const { control, register } = useForm<FormData>({
resolver: zodResolver(formSchema),
});
const { fields, append, remove } = useFieldArray({
control,
name: 'items',
});
return (
<form>
{fields.map((field, index) => (
<div key={field.id}>
<input {...register(`items.${index}.name` as const)} />
<input type="number" {...register(`items.${index}.quantity` as const)} />
<button type="button" onClick={() => remove(index)}>削除</button>
</div>
))}
<button type="button" onClick={() => append({ name: '', quantity: 1 })}>
アイテムを追加
</button>
</form>
);
}
エラーハンドリング
const { formState: { errors } } = useForm<FormData>({
resolver: zodResolver(formSchema),
});
// 特定のフィールドのエラーを表示
{errors.name && <span className="error">{errors.name.message}</span>}
// すべてのエラーをまとめて表示
{Object.entries(errors).map(([field, error]) => (
<p key={field}>{field}: {error.message}</p>
))}
パフォーマンスの最適化
- 大規模フォームの場合は useFormContext を使用してプロップのドリルダウンを避ける
- フィールドを論理的なグループに分割する
- Zod スキーマで
z.coerceを使用して、不必要な再レンダリングを避ける
useFormContext の例
import { useFormContext } from 'react-hook-form';
function NameField() {
const { register, formState: { errors } } = useFormContext<FormData>();
return (
<div>
<input {...register('name')} />
{errors.name && <p>{errors.name.message}</p>}
</div>
);
}
// 親フォームは useFormContext をプロバイダーとして提供します
一般的な問題の修正
- 制御されていない警告:
controlledプロパティが一貫していることを確認し、null のデフォルト値を避ける - リゾルバエラー: Zod スキーマが
@hookform/resolversで必要な正しい型をエクスポートしていることを確認する - useFieldArray の問題: 動的配列キーには常に
field.idを使用し、配列のインデックスは使用しない - パフォーマンスの問題: フィールドを分離し、
useWatchの代わりにwatchを使い、必要な場合にのみコンポーネントをラップする