🥥

【React】React Hook FormのuseFieldArrayでcheckbox項目をswapする時の留意点

2022/11/29に公開

概要

React Hook Formでは、可変の配列項目を格納できるuseFieldArrayという機能があり、このuseFieldArrayには配列項目の順番を入れ替えるswapの機能もあります。今回はswapを使って以下のような画面をテーブルを作成しようとした時にハマったことのメモ書きです。

前提

使用したReact Hook Formのバージョンは7.34.2です。

ハマったこと

特にswapしなければ【React】react-hook-formで全てのチェックボックスのチェックする方法の記事にあるように、{...register(`windows.${index}`, {})}のような形でregisterを設定すれば問題ないのですが、この状態でswapを走らせると、値に配列項目が格納されてしまいました。
おそらく、swapの時はHow can I set checkbox values of dynamic array from server using React Hook Forms Field Arrayにあるような、複数チェックボックスを選択させるような形式を前提としているようですので、今回のように単純にbooleanだけ設定するケースの時は上手くいかないようです。

対応方針

  • React Hook Formのswapは使わず、愚直に配列の要素を指定位置に移動させるの記事を参考にして、入れ替え後の配列を作成して、formの値をreplaceします。
  • replaceすると、チェックボックスの表示が値に追従しなかったので、input要素にcheckedのプロパティを追加します。
  • checkedのプロパティを追加すると、チェック操作の時の値更新が上手く動かなくなったので、onChangeも個別に実装します。

実装サンプル

まずはフォームの定義です、配列の設定を用意します。

const fieldArrayName = "categories";
const { control, register, handleSubmit, formState, getValues } = useForm({
  mode: "onChange",
});
const { fields, append, remove, update, replace } = useFieldArray({
  control,
  name: fieldArrayName,
});

次にチェックボックスの実装です。今回はHTMLのinput要素を使用しています。
以下は配列のループの中でレンダリングする箇所です。

// ループの中でチェックボックスを実装
・
・
<input
  id={`${fieldArrayName}.${index}.sampleCheckBox`}
  type="checkbox"
  name={`${fieldArrayName}.${index}.sampleCheckBox`}
  {...prop.register(`${fieldArrayName}.${index}.sampleCheckBox`)}
  checked={getValues().categories[index].sampleCheckBox}
  onChange={(e) => {
    const updatedFlag = e.target.checked;
    update(index, {
      ...getValues().categories[index],
      sampleCheckBox: updatedFlag,
    });
  }}
/>

最後に入れ替えの部分のメソッドです。
入れ替え後の配列を生成後に、formをreplaceしています。

function upSort(index) {
  const movedArray = moveAt([...getValues().categories], index, index - 1);
  replace(movedArray);
}

function downSort(index) {
  const movedArray = moveAt([...getValues().categories], index, index + 1);
  replace(movedArray);
}

function moveAt(array, index, at) {
  if (index === at || index > array.length - 1 || at > array.length - 1) {
    return array;
  }

  const value = array[index];
  const tail = array.slice(index + 1);

  array.splice(index);

  Array.prototype.push.apply(array, tail);

  array.splice(at, 0, value);

  return array;
}

Discussion