🦔

【React Hook Form/Next.js】useFieldArrayでのremoveに条件分岐をつけるとバグが起きる!

2025/01/02に公開

今回はお仕事で遭遇した、RHFのuseFieldArrayでのバグを備忘録として残します。

useFieldArrayとは

ReactHookFormはフォームバリデーションを構築できる非常に便利なライブラリですが
その中でもuseFieldArrayは、動的にフィールドの項目を管理できる機能を提供します。
基本的な使い方については以下記事がわかりやすかったので参照してください。
https://curucuru.co.jp/blog/20240215_use_field_array

今回起きたバグ

useFieldArrayの中のremoveを使うと、動的にフィールドを減らすことができるのですが
今回は仕様上、フィールドが1つの場合はそのまま残したかったので、以下のような実装をしたところ
レンダリングが行われずvalueが更新されないバグが起きてしまいました。


import { useFieldArray, useForm } from "react-hook-form";

type FormValues = {
  fields: { name: string }[];
};

export default function MyForm() {
  const { control } = useForm<FormValues>({
    defaultValues: { fields: [{ name: "" }] },
  });
  const { fields, remove } = useFieldArray({
    control,
    name: "fields",
  });

  const handleRemove = (index: number) => {
    // ここでフィールドが1つより多い場合のみremove処理を行うようにしています。
    if (fields.length > 1) {
      remove(index);
    }
  };

  return (
    <form>
      {fields.map((field, index) => (
        <div key={field.id}>
          <input
            {...register(`fields.${index}.name`)}
            defaultValue={field.name}
          />
          <button type="button" onClick={() => handleRemove(index)}>
            Remove
          </button>
        </div>
      ))}
    </form>
  );
}

このコードではfields.length > 1の条件を追加して、
「最小1個のフィールドの時は削除しない」という意図がありますが、
この書き方は時々不正なレンダリングが起こり、不意な動作をすることがあります。
バリデーションやエラーが変なタイミングで発火されたり、エラーが表示されなかったりとUI上でも意図しない動作が起こってしまいます。

PJのコードでは、画像アップロードの動的フィールドを実装しており、画像がアップロードされたら「画像を追加」ボタンが表示されるのですが、
今回のバグで、何度か削除と追加を繰り返すと、画像をアップロードしてもレンダリングが行われず「画像を追加」のボタンが表示されなくなる問題が発生していました。

解決策

  1. フィールドをremoveで削除して、フィールドが0になったらappendでフィールドを1つ追加させる

条件分岐をつけてremoveを行なってしまうことが原因だったので、一度removeを実行した後に、appendでフィールドを追加させる。
これによりUI上では、「フィールドを最小1つ残しておく」の要件を満たすことができます。

const handleRemove = (index: number) => {
  remove(index);
  if (fields.length === 0) {
    append({ name: "" }); // 新しいフィールドを追加
  }
};
  1. 別のところで制御する

最小のフィールド数を保持する別の設計を考える。
例えば、最小フィールドを削除できないようにボタンを無効化するなどが使えそうです。

<button
  type="button"
  onClick={() => handleRemove(index)}
  disabled={fields.length === 1} // フィールドが1つの場合は無効化
>
  Remove
</button>

まとめ

ReactHookFormのuseFieldArrayは便利でよく使いますが、今回思わぬバグにハマりました。
利用方法を間違えると意図しないバグを起こすことがあるので、正しく理解し適切な場所でロジックを組み実装していかなければならないなと感じました。

この件について全然記事が見当たらなかったので書きましたが、
他の方法があったり、参考記事がありましたらコメントくださると幸いです。

Discussion