概要
useFieldArray を使うと、フォーム内に動的なフィールドのリストを管理できる。
メンバー追加フォームや、複数行の入力テーブルなど、件数が可変のフォームに使う。
インストール
npm install react-hook-form zod @hookform/resolvers
実装例(メンバー追加フォーム)
// src/components/MemberForm.tsx
"use client";
import { useForm, useFieldArray } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const memberSchema = z.object({
name: z.string().min(1, "名前を入力してください"),
email: z.string().email("正しいメールアドレスを入力してください"),
});
const schema = z.object({
members: z.array(memberSchema).min(1, "メンバーを1人以上追加してください"),
});
type FormValues = z.infer<typeof schema>;
export function MemberForm() {
const {
register,
control,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: { members: [{ name: "", email: "" }] },
});
const { fields, append, remove } = useFieldArray({
control,
name: "members",
});
const onSubmit = (data: FormValues) => {
console.log("送信:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
{fields.map((field, index) => (
// key には field.id を使う(data の id ではない)
<div key={field.id} className="flex gap-2 rounded border p-3">
<div className="flex-1 space-y-2">
<input
{...register(`members.${index}.name`)}
placeholder="名前"
className="w-full rounded border px-3 py-2 text-sm"
/>
{errors.members?.[index]?.name && (
<p className="text-xs text-red-600">{errors.members[index].name.message}</p>
)}
<input
{...register(`members.${index}.email`)}
placeholder="メールアドレス"
className="w-full rounded border px-3 py-2 text-sm"
/>
{errors.members?.[index]?.email && (
<p className="text-xs text-red-600">{errors.members[index].email.message}</p>
)}
</div>
<button
type="button"
onClick={() => remove(index)}
disabled={fields.length === 1}
className="self-start rounded border px-2 py-1 text-sm text-red-600 disabled:opacity-30"
>
削除
</button>
</div>
))}
{errors.members?.root && (
<p className="text-xs text-red-600">{errors.members.root.message}</p>
)}
<div className="flex gap-2">
<button
type="button"
onClick={() => append({ name: "", email: "" })}
className="rounded border px-4 py-2 text-sm"
>
メンバーを追加
</button>
<button
type="submit"
className="rounded bg-blue-600 px-4 py-2 text-sm text-white"
>
送信
</button>
</div>
</form>
);
}
ポイント
useFieldArrayのfieldsには RHF が自動生成したidが付与される。keyには必ずfield.idを使うappend({ name: "", email: "" })で末尾にフィールドを追加、remove(index)でインデックス指定で削除できるregisterのフィールド名はmembers.${index}.nameのように動的に指定する- Zod の
z.array(memberSchema).min(1)で配列全体のバリデーション、各要素はmemberSchemaで検証される - 配列全体のエラーは
errors.members?.rootで参照する