formik の FieldArray を扱うときに、key に index を使用したくない

2020-04-08

linter 的に良くないので、回避方法を模索しました。

ちなみに、公式サイトは諦めているみたいです、楽だしね。

import {
  Field,
  FieldArray,
  FieldArrayConfig,
  Form,
  Formik,
  FormikConfig,
} from "formik";
import React, { FC, useCallback, useMemo, useState } from "react";

import uniqid from "uniqid";

type Hoge = {
  fuga: string;
  piyo: number;
};

type AppValues = {
  hoges: Hoge[];
};

type HogesProperty = {
  handleClickRemoveButton: () => void;
  key: string;
};

const App: FC = () => {
  const initialValues = useMemo<FormikConfig<AppValues>["initialValues"]>(
    () => ({
      hoges: [],
    }),
    [],
  );
  const handleSubmit = useCallback<FormikConfig<AppValues>["onSubmit"]>(
    (values) => {
      console.log(values);
    },
    [],
  );
  const [hogesProperties, setHogesProperties] = useState<HogesProperty[]>([]);
  const renderHoges = useCallback<NonNullable<FieldArrayConfig["children"]>>(
    ({ push, remove }) => {
      const handleClickAddButton = () => {
        const key = uniqid();

        push({
          fuga: "",
          piyo: 0,
        });

        setHogesProperties((prevHogesProperties) =>
          ([] as typeof hogesProperties).concat(prevHogesProperties, [
            {
              key,
              handleClickRemoveButton: () => {
                setHogesProperties((prevHogesProperties) => {
                  const index = prevHogesProperties.findIndex(
                    ({ key: prevKey }) => key === prevKey,
                  );

                  // かなり強引
                  remove(index);

                  return prevHogesProperties.filter(
                    (_, prevIndex) => index !== prevIndex,
                  );
                });
              },
            },
          ]),
        );
      };
      const hogesFields = hogesProperties.map(
        ({ handleClickRemoveButton, key }, index) => (
          <div key={key}>
            <Field name={`hoges.${index}.fuga`} />
            <Field name={`hoges.${index}.piyo`} type="number" />
            {key}
            <button onClick={handleClickRemoveButton} type="button">
              remove
            </button>
          </div>
        ),
      );

      return (
        <div>
          <button onClick={handleClickAddButton} type="button">
            add
          </button>
          {hogesFields}
        </div>
      );
    },
    [hogesProperties, setHogesProperties],
  );

  return (
    <Formik initialValues={initialValues} onSubmit={handleSubmit}>
      <Form>
        <FieldArray name="hoges">{renderHoges}</FieldArray>
        <button type="submit">submit</button>
      </Form>
    </Formik>
  );
};

export default App;

remove を呼ぶタイミングがかなり強引なんですが、ここしか思いつかず…。

container を量産したくないケースでは使えそうですが、パフォーマンスはあまり良くなさそうです。