🕟

【React Hook Form】useFieldArrayで付与されるkey属性の値について(2024年3月時点)

2024/03/15に公開

概要

React Hook Formで配列の値を制御したい時、useFieldArrayを使うと思いますが、その際に使われるkey属性の値内容についてメモ書きします。なお2024年3月時点で、ドキュメントName of the attribute with autogenerated identifier to use as the key prop. This prop is no longer required and will be removed in the next major version.と記載がある通り、今後の更新で動作が変わる可能性が高いので、あくまで現時点での動作という形になります。

前提

  • 今回使用したReact Hook Formのバージョンは7.51.0になります。

動作内容

React Hook Form useFieldArray overwriting my own idのstackoverflowの記事にある通り、key属性に設定した項目名(デフォルトだと「id」)について、useFieldArrayのフィールドでは値を自動で上書きを行う動作になっているようです。ただ、元のフォームの値には反映されずに、あくまでuseFieldArrayでのフィールドのみに適用されるようです。もし、元のフォームで「id」属性を使いたい場合は、useFieldArrayで別名のkeyを指定したほうがベターだと思います。

実装サンプル

今回、uiはshadcn/uiを使用してサンプルを実装しました。
以下の実装では「id」属性をあえてformでも使用するようにして、値がどのように設定されるか確認しました。

export const sampleInputFormSchema = z.object({
  memoList: z.array(
    z.object({
      id: z.string().optional(),
      title: z.string().optional(),
      contents: z.string().optional(),
    })
  ),
});

export const SampleInputComponent: FC = () => {
  const form = useForm<z.infer<typeof sampleInputFormSchema>>({
    resolver: zodResolver(sampleInputFormSchema),
    mode: "onSubmit",
    reValidateMode: "onSubmit",
    defaultValues: {
      memoList: [{ id: "test" }],
    },
  });
  const control = form.control;
  const {
    fields: memoFields,
    prepend: memoPrepend,
    remove: memoRemove,
  } = useFieldArray({
    control,
    name: "memoList",
  });

  const submitFunc = async (
    data: z.infer<typeof sampleInputFormSchema>
  ) => {
    // useFieldArrayのfieldsではformの値ではなく自動で割り当てられたid値で上書きされる
    console.log(memoFields);
    // 元のformにはuseFieldArrayのid値は反映されない
    console.log(data);
  };

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(submitFunc)}>
        <div className="flex flex-col gap-4 ml-2">
          <div className="flex flex-row gap-2 items-center">
            <FormLabel>メモ</FormLabel>
            <Button
              onClick={() => {
                memoPrepend({
                  id: "test",
                });
              }}
              type="button"
            >
              メモを追加
            </Button>
          </div>
          {memoFields.map((memo, index) => (
            <>
              {/* ここのidはuseFieldArrayで割り当てられる値 */}
              <div key={memo.id}>
                <FormField
                  control={form.control}
                  name={`memoList.${index}.title`}
                  render={({ field }) => (
                    <FormItem>
                      <FormControl>
                        <Input
                          {...field}
                          placeholder="メモのタイトルを入力"
                        />
                      </FormControl>
                    </FormItem>
                  )}
                />
                <FormField
                  control={form.control}
                  name={`memoList.${index}.contents`}
                  render={({ field }) => (
                    <FormItem>
                      <FormControl>
                        <Textarea
                          {...field}
                          placeholder="メモの内容を入力"
                        />
                      </FormControl>
                    </FormItem>
                  )}
                />
                <Button
                  onClick={() => {
                    memoRemove(index);
                  }}
                  type="button"
                >
                  メモを削除
                </Button>
              </div>
            </>
          ))}
        </div>
        <Button type="submit">
          <p>結果を登録</p>
        </Button>
      </form>
    </Form>
  );
};

Discussion