第188章:useFieldArray
「電話番号が何個もある」「SNSリンクをいくつも登録したい」「メンバーを追加したい」みたいに、入力欄が増えたり減ったりするフォームってありますよね?📋
それを超気持ちよく作れるのが React Hook Form の useFieldArray です💪💖
まずイメージつかも!👀✨(Mermaid図解)
今日作るもの🎀
「ゼミメンバー登録フォーム」
- メンバーを「+追加」できる➕
- いらないメンバーは「削除」できる🗑️
- 送信すると、配列で全部まとまって取れる📦✨
もしまだ入ってなければインストール(念のため)💿
(前の章で入れてるならスキップでOKだよ〜🙆♀️)
npm i react-hook-form
実装:useFieldArray の基本セット(TS対応)🧁
src/FieldArrayDemo.tsx を作る✨
import { useFieldArray, useForm } from "react-hook-form";
type Member = {
name: string;
grade: number; // 学年(数字)
note: string;
};
type FormValues = {
members: Member[];
};
export function FieldArrayDemo() {
const {
register,
control,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({
defaultValues: {
members: [{ name: "", grade: 1, note: "" }],
},
mode: "onBlur",
});
const { fields, append, remove } = useFieldArray({
control,
name: "members",
});
const onSubmit = (data: FormValues) => {
console.log("送信データ:", data);
alert("送信できたよ〜✨ コンソール見てね👀");
};
return (
<div style={{ maxWidth: 720, margin: "40px auto", padding: 16 }}>
<h1>ゼミメンバー登録フォーム 🌸</h1>
<p>「+追加」で入力欄が増えるよ〜➕✨</p>
<form onSubmit={handleSubmit(onSubmit)}>
<div style={{ display: "grid", gap: 12 }}>
{fields.map((field, index) => {
const nameError = errors.members?.[index]?.name?.message;
const gradeError = errors.members?.[index]?.grade?.message;
return (
<div
key={field.id}
style={{
border: "1px solid #ddd",
borderRadius: 12,
padding: 12,
}}
>
<h3 style={{ marginTop: 0 }}>メンバー {index + 1} 👤</h3>
<div style={{ display: "grid", gap: 8 }}>
<label>
名前 ✍️
<input
style={{ display: "block", width: "100%", padding: 8 }}
placeholder="例)さくら"
{...register(`members.${index}.name`, {
required: "名前は必須だよ〜🥺",
minLength: { value: 2, message: "2文字以上にしてね😊" },
})}
/>
{nameError && (
<p style={{ margin: "6px 0 0", color: "crimson" }}>
{nameError}
</p>
)}
</label>
<label>
学年 🎓
<input
style={{ display: "block", width: "100%", padding: 8 }}
type="number"
{...register(`members.${index}.grade`, {
valueAsNumber: true,
min: { value: 1, message: "1〜4の範囲にしてね🙂" },
max: { value: 4, message: "1〜4の範囲にしてね🙂" },
})}
/>
{gradeError && (
<p style={{ margin: "6px 0 0", color: "crimson" }}>
{gradeError}
</p>
)}
</label>
<label>
ひとこと 💬(任意)
<input
style={{ display: "block", width: "100%", padding: 8 }}
placeholder="例)カフェ巡りが好き☕"
{...register(`members.${index}.note`)}
/>
</label>
<div style={{ display: "flex", gap: 8 }}>
<button
type="button"
onClick={() => remove(index)}
disabled={fields.length === 1}
style={{
padding: "8px 12px",
borderRadius: 10,
border: "1px solid #ddd",
cursor: "pointer",
}}
>
このメンバーを削除 🗑️
</button>
{fields.length === 1 && (
<span style={{ alignSelf: "center", opacity: 0.7 }}>
※1人は残すよ🙂
</span>
)}
</div>
</div>
</div>
);
})}
</div>
<div style={{ display: "flex", gap: 8, marginTop: 12 }}>
<button
type="button"
onClick={() => append({ name: "", grade: 1, note: "" })}
style={{
padding: "10px 14px",
borderRadius: 12,
border: "1px solid #ddd",
cursor: "pointer",
}}
>
+ メンバー追加 ➕✨
</button>
<button
type="submit"
style={{
padding: "10px 14px",
borderRadius: 12,
border: "1px solid #ddd",
cursor: "pointer",
fontWeight: "bold",
}}
>
送信する 📩
</button>
</div>
</form>
<p style={{ marginTop: 16, opacity: 0.75 }}>
※送信後、ブラウザのコンソール(F12)で配列データが見えるよ👀✨
</p>
</div>
);
}
src/App.tsx から呼び出す🍰
import { FieldArrayDemo } from "./FieldArrayDemo";
export default function App() {
return <FieldArrayDemo />;
}
超大事ポイント3つ🔥(ここでハマりやすい!)
-
keyはfield.idを使う 🙅♀️indexはなるべく避けてね → 入力中に順番がズレたりして、フォームが壊れがち😵💫 -
fieldsは「表示用の設計図」 🧩fields自体は入力値そのものじゃないよ! 「今の入力値」を見たい時はwatch()やgetValues()を使う感じ👀✨ -
defaultValuesは最初に形を作る 🧱 配列が空だと、最初は入力欄が何も出なくて「???」ってなることあるよ〜🙂
ちょい応用(できたら強い)💪✨
useFieldArray には便利ワザがいっぱい!🎮
prepend():先頭に追加🥇insert(index, value):途中に追加📍swap(a, b)/move(from, to):並び替え🔀replace(newArray):ごっそり置き換え🧹
ミニ練習🎯(5分でOK)
- 「学年」をプルダウン(
<select>)にしてみよ🎓 - 「並び替え(上へ/下へ)」ボタンを追加して
move()を使ってみよ🔼🔽 noteをtextareaにして、長文入力にしてみよ📝✨
次の章(第189章)では、RHFがなぜ入力しても無駄に再レンダリングしにくいのかとか、もっと軽くする考え方をやるよ〜⚡😊