FormikのFormArrayを使ってみる

## はじめに
業務でFormikを使っているのですが、フォームで配列の要素を簡単に扱えるFieldArrayというものを知ったので、その使い方を紹介します。

## やりたいこと
以下のようなタスク管理ができるアプリを考えます。
<img width='80%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/f6a614f3-ee84-5be4-b421-a6393bad9064.png'>

要素を追加したり、削除したりするロジックを考えたりするのが少しめんどくさそうです。

こういうときにFieldArrayが役に立ちます。

## 基本形
冒頭に示したコードの一部を抜粋して記載します。
```react
<FieldArray name="tasks">
  {({ push, remove }) => (
    <>
      {values.tasks.map((_, index) => (
        <div key={index}>
          <Field
            name={`tasks.${index}.taskName`}
            placeholder="タスク名"
          />
          <ErrorMessage
            name={`tasks.${index}.taskName`}
            component="div"
          />
          <button type="button" onClick={() => remove(index)}>
            削除
          </button>
        </div>
      ))}
      <button type="button" onClick={() => push({ taskName: "" })}>
        タスクを追加
      </button>
    </>
  )}
</FieldArray>
```
一つずつ解説します。

### nameの指定
ベースになるFieldArrayコンポーネントの`name`属性と、繰り返しの要素の`name`属性は対応させる必要があります。

一般化すると次のようになります。
```React
<FieldArray name="baseName">
  {({ push, remove }) => (
    <>
    {array.map((value,index) => (
      <div key={value}>
        {/* stringなどの配列の場合 */}
        <Field name={`baseName.${index}`} />
        {/* オブジェクトの配列の場合 */}
        <Field name={`baseName.${index}.propertyName`} />
      </div>
    ))}
    </>
  )}
</FieldArray>
```

今回の例ではFieldArrayコンポーネントの`name`属性に`tasks`を指定しています。
繰り返しの要素の`name`にはベースとなる`name`属性に加えて、何番目の要素であるかをindexを使って明示します。

普通の配列要素であればこれだけで取得できます。
今回はオブジェクトの配列であるので、さらにプロパティ名を指定しています。

FieldArrayコンポーネントの`name`属性に指定した値で、繰り返し要素を取りまとめる配列が作られるものだと考えれば、繰り返し要素に指定する`name`属性がわかりやすくなるかと思います。

### ヘルパーの利用
繰り返し要素の描画方法には、今回のような子要素に関数を指定する方法と、FieldArrayコンポーネントの`render`に関数を指定する方法、`component`にコンポーネントを指定する方法があります。

それらのいずれを選択しても、繰り返し要素の操作をするためのオブジェクト（arrayHelpers）が引数として渡されます。

arrayHelpersには配列操作のためのメソッドが用意されており、基本的には使用したいものを分割代入で取得するのが一般的です。

今回の例で使用している`push`、`remove`のほか、`insert`、`move`あたりがよく使う処理かと思います。

`push`は末尾に追加、`remove`は指定したインデックスの要素を削除します。
`insert`は指定したインデックスへ要素を追加でき、`move`は指定したインデックスから別のインデックスへ要素を移動させます。

使用できるメソッドの一覧は以下の公式サイトから確認できます。

https://formik.org/docs/api/fieldarray#fieldarray-helpers

これらの処理を各ボタン要素と組み合わせることで、ロジックを自分で実装することなく、要素の追加削除、移動ができるようになります。

以上の要素を抑えることで、簡単に配列として画面項目を扱うことができるようになります。

## ソースコード全文
以下の折りたたみに、今回のソースコードを全文記載しておきます。
Yupを使ったバリデーションについても記載しているので、もしよければ参考にしてみてください

<details><summary>ソースコード全文</summary>

```react
import React from "react";
import { Formik, Form, FieldArray, Field, ErrorMessage } from "formik";
import * as Yup from "yup";
interface Task {
  taskName: string;
}

interface TaskFormValues {
  tasks: Task[];
}
const initialValues: TaskFormValues = {
  tasks: [],
};

const validationSchema = Yup.object({
  tasks: Yup.array()
    .of(
      Yup.object({
        taskName: Yup.string().required("タスク名は必須です"),
      })
    )
    .min(1, "少なくとも1つのタスクが必要です"),
});

const TaskManager: React.FC = () => {
  const handleSubmit = (values: typeof initialValues) => {
    console.log(values);
    // ここでバックエンドへのデータ送信などを行います
  };

  return (
    <div>
      <h1>シンプルタスク管理</h1>
      <Formik
        initialValues={initialValues}
        validationSchema={validationSchema}
        onSubmit={handleSubmit}
      >
        {({ values, errors }) => (
          <Form>
            <FieldArray name="tasks">
              {({ push, remove }) => (
                <>
                  {values.tasks.map((_, index) => (
                    <div key={index}>
                      <Field
                        name={`tasks.${index}.taskName`}
                        placeholder="タスク名"
                      />
                      <ErrorMessage
                        name={`tasks.${index}.taskName`}
                        component="div"
                      />
                      <button type="button" onClick={() => remove(index)}>
                        削除
                      </button>
                    </div>
                  ))}
                  <button type="button" onClick={() => push({ taskName: "" })}>
                    タスクを追加
                  </button>
                </>
              )}
            </FieldArray>
            {typeof errors.tasks === "string" && <div>{errors.tasks}</div>}
            <div>
              <button type="submit">保存</button>
            </div>
          </Form>
        )}
      </Formik>
    </div>
  );
};

export default TaskManager;

```

</details>


## まとめ
余計なことを考えずとも、画面項目を配列で扱えるのはかなり便利だと感じました。
Formikは業務でよく使う割に、深い理解ができていないので、今後も色々な使い方を紹介できればと思っています。