はじめに
業務でFormikを使っているのですが、フォームで配列の要素を簡単に扱えるFieldArrayというものを知ったので、その使い方を紹介します。
やりたいこと
要素を追加したり、削除したりするロジックを考えたりするのが少しめんどくさそうです。
こういうときにFieldArrayが役に立ちます。
基本形
冒頭に示したコードの一部を抜粋して記載します。
<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
属性は対応させる必要があります。
一般化すると次のようになります。
<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
は指定したインデックスから別のインデックスへ要素を移動させます。
使用できるメソッドの一覧は以下の公式サイトから確認できます。
これらの処理を各ボタン要素と組み合わせることで、ロジックを自分で実装することなく、要素の追加削除、移動ができるようになります。
以上の要素を抑えることで、簡単に配列として画面項目を扱うことができるようになります。
ソースコード全文
以下の折りたたみに、今回のソースコードを全文記載しておきます。
Yupを使ったバリデーションについても記載しているので、もしよければ参考にしてみてください
ソースコード全文
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;
まとめ
余計なことを考えずとも、画面項目を配列で扱えるのはかなり便利だと感じました。
Formikは業務でよく使う割に、深い理解ができていないので、今後も色々な使い方を紹介できればと思っています。