React Hook Form の useFieldArray が同期されない!? 複数コンポーネントで使用したときにハマった話と対処法
React Hook Formを使ってフォーム開発をしているとき、「あれ?配列に要素を追加したのに反映されないのでは?」と思うような予期せぬ挙動に遭遇しました。
具体的には、useFieldArrayを使ったフォームを作っていたのですが、複数のコンポーネントで同じフィールド名に対してuseFieldArrayをそれぞれ呼び出した結果、片方で追加した内容がもう片方に反映されないという現象が起きたのです。
最初は「自分の実装がまずかったのか?」と思ったのですが、これはReact Hook Form の仕様通りの動作でした。
何が起きたのか?
以下は、実際に遭遇した問題を再現するために用意したシンプルなコード例です。
実際のプロジェクトではもう少し複雑な構成でしたが、話をわかりやすくするために簡略化しています。
-
Formの中でItemListを2つ表示する - 各
ItemListはそれぞれ同じフィールドであるitemsに対してuseFieldArrayを使用する
"use client";
import { useFieldArray, useForm, Control } from "react-hook-form";
type FormValues = {
items: Array<{ text: string }>;
};
const Form = () => {
const { control } = useForm<FormValues>({
defaultValues: {
items: [{ text: "Item 1" }, { text: "Item 2" }, { text: "Item 3" }],
},
});
return (
<div>
<ItemList control={control} />
<ItemList control={control} />
</div>
);
};
const ItemList = ({ control }: { control: Control<FormValues> }) => {
const { fields, append } = useFieldArray({
name: "items",
control,
});
return (
<div>
<div>
{fields.map((field) => (
<div key={field.id}>{field.text}</div>
))}
</div>
<hr />
<div>
<button onClick={() => append({ text: "New Item" })}>Add</button>
</div>
</div>
);
};
※見やすいようにスタイルを調整しています。

初期状態
この状態で赤いリストの「Add」ボタンをクリックすると、赤いリストにだけ「New Item」が追加され、青いリストには変化がありません。

赤いリストのAddボタンをクリック
この状態でgetValues('items')を使ってフォームの値を確認してみると、items配列には新しい項目がきちんと追加されています。
これはつまり、appendを実行したことで内部の状態は更新されているものの、別のコンポーネントで呼び出したuseFieldArrayのfieldsはその変更を検知せず、再レンダリングもされないということです。
ちなみに、赤いリストの「Add」ボタンをクリックしたあとに青いリストの「Add」ボタンをクリックすると、以下のようになります。

赤いリストのAddボタンをクリック → 青いリストのAddボタンをクリック
わけわからん…(笑)
おそらく、append時に最新の状態を取得した上で新しい要素を追加していて、今度は赤いリストの方が再レンダリングされなかったのだと思われます。
よく見るとドキュメントに…
Each useFieldArray is unique and has its own state update, which means you should not have multiple useFieldArray with the same name.
useFieldArray は呼び出すたびに独立した状態を持つため、同じフィールド名に対して複数使うことは推奨されていません。
ちゃんと書いてある…(笑)
useFieldArrayが返すfieldsが内部的に独立した状態を保持しているため、複数箇所で呼び出すと同期されないということらしいです。
でも実際、複数のコンポーネントで使いたいことはある
とはいえ、現実には同じ配列に対して何らかの操作を行いたいケースも、少ないながら存在します。
解決策としてはドキュメントに書いてある通り、複数回呼び出すのが問題なら1回だけ呼び出せばいいわけです。
親のコンポーネントで一度だけuseFieldArrayを呼び出し、その戻り値をprops経由で渡す方法が、シンプルでわかりやすいです。
もしコンポーネントの階層が深く、propsの受け渡しが煩雑になる場合には、ReactのContextを使って戻り値を共有するという方法もあります。
重要なのは、どちらの方法を取るにしてもuseFieldArrayの呼び出しは1回だけにするという点です。
おわりに
結局のところ、公式ドキュメントをちゃんと読みましょう、という元も子もない話ではあるのですが、実際に自分で試してハマってみないと気づけないことも多いです。
今回のように一見「バグかな?」と思うような挙動でも、コードを細かく追ってみると「なるほど、こうなっていたのか」と理解が深まります。
内部の動作や仕組みに少し踏み込んでみると、ライブラリの使い方だけでなく設計の思想まで垣間見えることがあり、そうした探求もまた開発の楽しさの一つだと思います。
ちょっと株式会社(chot-inc.com)のエンジニアブログです。 フロントエンドエンジニア募集中! カジュアル面接申し込みはこちらから chot-inc.com/recruit/iuj62owig
Discussion