Formikを導入する

Formikの使い勝手が良すぎるので、備忘録がてらQiitaに残します。
（↓ 分かりやすく説明しているYoutubeもたくさんアップされているので、参考にぜひ
https://youtu.be/FD50LPJ6bjE）

## 目次
- [Formikの導入](https://qiita.com/drafts/424ef83dba427bd0b2b8#formik%E3%81%AE%E5%B0%8E%E5%85%A5)
- [各入力エリアにFormikを適用する](https://qiita.com/drafts/424ef83dba427bd0b2b8#%E5%90%84%E5%85%A5%E5%8A%9B%E3%82%A8%E3%83%AA%E3%82%A2%E3%81%ABformik%E3%82%92%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B)
- [配列に対応する](https://qiita.com/drafts/424ef83dba427bd0b2b8#%E9%85%8D%E5%88%97%E3%81%AB%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B)
- [特定の入力エリアの値が変わったことを検知し、表示を変更する](https://qiita.com/drafts/424ef83dba427bd0b2b8#%E7%89%B9%E5%AE%9A%E3%81%AE%E5%85%A5%E5%8A%9B%E3%82%A8%E3%83%AA%E3%82%A2%E3%81%AE%E5%80%A4%E3%81%8C%E5%A4%89%E3%82%8F%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8%E3%82%92%E6%A4%9C%E7%9F%A5%E3%81%97%E8%A1%A8%E7%A4%BA%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B)
- [追加、削除](https://qiita.com/drafts/424ef83dba427bd0b2b8#%E8%BF%BD%E5%8A%A0%E5%89%8A%E9%99%A4)
- [バリデーションを適用する](https://qiita.com/drafts/424ef83dba427bd0b2b8#%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B)

## Formikの導入
まずはライブラリをインストールしていきます。
（公式サイトはこちら → https://jaredpalmer.com/formik/）

```shell
$ npm i formik
```

続いて、`<form>` タグを `<Formik>` へ変更します。
`<Formik>`タグに関する公式サイトはこちら → [`<Formik />`](https://jaredpalmer.com/formik/docs/api/formik)

```javascript
import { Form, Formik } from "formik"

export const App: React.FC<Props> = () => {
  return (
    // 変更前
    <form>
    ...
    </form>

    // 変更後
    <Formik
      initialValues={}
      onSubmit={(values, actions) => {
        // Submit した時の記述
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2))
          actions.setSubmitting(false)
        }, 1000)
      }}
    >
      <Form>
      ...
      </Form>
    </Formik>
  )
}
```

また、初期値と型の定義をして下準備を整えておきます。
今回は下のようなデータを扱うことにしていきます。

- カテゴリ「犬」、「猫」、「うさぎ」から選ぶ
- 「ユーザー名」、「メールアドレス」
- `questions` は何個でも入力することができ、追加・削除ができる
- カテゴリを選択直後、`question` 内の動物タイプがカテゴリごとに描画される
- 動物カテゴリ「dogA」「catA」「rabbitA」を選択した時だけ、`questionB`を描画する

```javascript
type Category = "dog" | "cat" | "rabbit"

// Formで使用する型
export type BaseValues = {
  userName: string
  email: string
  category: Category
  questions: Question[]
}

export type Question = {
  dogType?: "dogA" | "dogB" | "dogC" | ""
  catType?: "catA" | "catB" | "catC" | ""
  rabbitType?: "rabbitA" | "rabbitB" | "rabbitC" | ""
  questionA: string
  questionB?: string
}
```

初期値を定義し、`<Formik>`の`initialValues`にセットする

```javascript
const initialValues = {
  category: "dog" as Category,
  userName: "",
  email: "",
  questions: [
    {
      questionA: "",
    },
  ],
}

...

    <Formik
      // 変更後
      initialValues={initialValues}
      onSubmit={(values, actions) => {}}
    >
      ...
    </Formik>
```


## 各入力エリアにFormikを適用する
テキストエリア、セレクトボックスをそれぞれ、Formikの`<Field>`タグに差し替えます。
バリデーション用のエラーメッセージは、一旦後回しにします。
`<Field />`タグに関する公式サイトはこちら → [`<Field />`](https://jaredpalmer.com/formik/docs/api/field)
`name`を正しくセットすることを忘れずに！


```javascript
import { Form, Formik, Field } from "formik"

export const App: React.FC<Props> = () => {
  return (
    ...
    // 変更前
    <input type="text" placeholder="名前" />
    ...
    // 変更後
    <Field
      type="text"
      name="userName"
      placeholder="名前"
    />

    ...

    // 変更前
    <select>
      <option selected>犬</option>
      <option>猫</option>
      <option>うさぎ</option>
    </select>
    ...
    // 変更後
    <Field as="select" name="category">
      <option value="dog">犬</option>
      <option value="cat">猫</option>
      <option value="rabbit">うさぎ</option>
    </Field>
    ...
  )
}
```

全て差し替えたところでSubmitすると、`onSubmit`の引数`values`に入力した値が入っていることを確認できます。

<img width="467" alt="1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/eca3fc0e-2e00-1ed0-0a90-cac951124ea5.png">

## 配列に対応する
配列データはFormikの`<FieldArray />`を使っていきます。
`<FieldArray />`タグに関する公式サイトはこちら → [`<FieldArray />`](https://jaredpalmer.com/formik/docs/api/fieldarray)
今回は`questions`に、型`Question`のオブジェクトを配列で管理することにしています。

```json
"data": [
  {
    "dogType": "dogA",
    "questionA": "foo",
    "questionB": "hoo"
  },
  {
    "dogType": "dogB",
    "questionA": "bar",
    "questionB": "moo"
  },
  ...
],
```

```javascript
import { FieldArray, useFormikContext } from "formik"

export const Questions: React.FC<Props> = () => {
  const { values } = useFormikContext<BaseValues>()

  return (
    <FieldArray
      name="questions"
      render={() => (
        <div>
          {values.questions.map((question, index) => (
            <div key={index}>
              <PetType fieldName={`questions.${index}`} />
              <Details fieldName={`questions.${index}`} question={question} />
            </div>
          ))}
        </div>
      )}
    />
  )
}
```

`<FieldArray />`に`name`をセットし、`render`の中で `values.questions` を`map`でループさせています。
入力エリアは別コンポーネントで管理するようにし、子コンポーネントには`name`を渡すようにしました。
1レコードごとのそれぞれの値は、`questions.${index}.XXX` で参照できます。

子コンポーネントの方もお見せします。

```javascript
import { Field } from "formik"

type Props = {
  fieldName: string
  children?: never
}

export const PetType: React.FC<Props> = ({ fieldName }) => {
  return (
    <div>
      ...
      <div>タイプ</div>
      <Field as="select" name={`${fieldName}.dogType`}>
        <option value="">選択してください</option>
        <option value="dogA">dogA</option>
        <option value="dogB">dogB</option>
        <option value="dogC">dogC</option>
      </Field>
      ...
    </div>
  )
}
```

## 特定の入力エリアの値が変わったことを検知し、表示を変更する
今回下の用件で作成していきます。

```text
カテゴリを選択した直後、各カテゴリに対応する入力エリアを描画させる
```

別コンポーネントで変更された値を検知するのに、`useFormikContext()`を使って、フォームの値を取得します。

```javascript
import { Field, useFormikContext } from "formik"
import { BaseValues } from "./App"

type Props = {
  children?: never
  fieldName: string
}

export const PetType: React.FC<Props> = ({ fieldName }) => {
  const { values } = useFormikContext<BaseValues>()

  return (
    ...
    <div>タイプ</div>
    <div>
      {values.category === "dog" && (
        <Field as="select" name={`${fieldName}.dogType`}>
          <option value="">選択してください</option>
          <option value="A">A</option>
          <option value="B">B</option>
          <option value="C">C</option>
        </Field>
      )}
      {values.category === "cat" && (
        <Field as="select" name={`${fieldName}.catType`}>
          <option value="">選択してください</option>
          <option value="A">A</option>
          <option value="B">B</option>
          <option value="C">C</option>
        </Field>
      )}
      {values.category === "rabbit" && (
        <Field as="select" name={`${fieldName}.rabbitType`}>
          <option value="">選択してください</option>
          <option value="A">A</option>
          <option value="B">B</option>
          <option value="C">C</option>
        </Field>
      )}
    </div>
  )
}
```

ただ、これだと入力エリアの切り替えは上手くいきましたが、カテゴリを何回か切り替えてタイプ別ドロップダウンも変更を続けた時、
切り替える前の値が残ってしまっています。
正しくは、選択中のカテゴリだけの動物タイプの値を取得し、選択外は取得させたくないので、一手間加える必要があります。

![3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/ec403fdd-7c41-8845-a158-3a3ccd39c492.gif)

ぐぬぬ、、そうすると…

私はこう実装しました。

```javascript
import { useField, useFormikContext } from "formik"
import { BaseValues, Category } from "./App"

type Props = {
  children?: never
}

export const AnimalCategory: React.FC<Props> = () => {
  const { values, setFieldValue } = useFormikContext<BaseValues>()
  const [field] = useField<Category>("category")

  const handleChange = (value: Category): void => {
    setFieldValue("category", value)

    values.questions.forEach((_q, index) => {
      switch (value) {
        case "dog":
          setFieldValue(`questions.${index}.dogType`, "")
          setFieldValue(`questions.${index}.catType`, undefined)
          setFieldValue(`questions.${index}.rabbitType`, undefined)
          break
        case "cat":
          setFieldValue(`questions.${index}.dogType`, undefined)
          setFieldValue(`questions.${index}.catType`, "")
          setFieldValue(`questions.${index}.rabbitType`, undefined)
          break
        case "rabbit":
          setFieldValue(`questions.${index}.dogType`, undefined)
          setFieldValue(`questions.${index}.catType`, undefined)
          setFieldValue(`questions.${index}.rabbitType`, "")
          break
      }
    })
  }

  return (
    <div>
      <div>カテゴリ</div>
      <select
        {...field}
        onChange={(event) => {
          handleChange(event.target.value as Category)
        }}
        onBlur={(event) => {
          handleChange(event.target.value as Category)
        }}
      >
        <option value="dog">犬</option>
        <option value="cat">猫</option>
        <option value="rabbit">うさぎ</option>
      </select>
    </div>
  )
}
```

`setFieldValue`を使うと手動で値の変更などができます。カテゴリが変更された時点、つまり、`onChange()`が走った時点で、
該当の動物タイプの値をセットし、且つ、除外するものは`undefined`をセットします。
`undefined`に指定するとvaluesに含まれないので、Submitすると除外された状態でPOSTなりPUTなりできます。

上記では`switch`で値をジャッジし、都度`setFieldValue`させるようにしています。
（ベストプラクティスご存知の方はぜひアドバイスください…！）
すると、期待通りのvaluesになってくれました。

![4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/ca9a8d26-8dd3-ca4c-a43b-b2f12e50277f.gif)



## 追加、削除
`<FieldArray />`の `push`や`remove`を使うと、配列データに追加・削除などの操作ができます。
FieldArray Helpersに関する公式サイトはこちら → [`FieldArray Helpers`](https://jaredpalmer.com/formik/docs/api/fieldarray#fieldarray-helpers)

```javascript
export const Questions: React.FC<Props> = () => {
  ...
  return (
    <FieldArray
      name="questions"
      render={({ remove, push }) => (
        <div>
          {values.questions.map((question, index) => (
            <div key={index}>
              // 変更後
              <button onClick={() => { remove(index) }}>削除</button>
              ...
            </div>
          ))}
          // 変更後
          <button
            onClick={(e) => {
              e.preventDefault()
              push({ questionA: "" })
            }}
          >
            追加
          </button>
        </div>
      )}
    />
  )
}
```

`render`で`remove`、`push`を渡し、削除・追加ボタンの`onClick`で使います。

![2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/030b5a1c-b94b-8161-a89d-74437caaa5e4.gif)

## バリデーションを適用する
各questionの `questionA`だけ入力必須にします。
Submitした時、`questionA`が空だったら該当の入力エリア下にエラーメッセージを表示させます。
バリデーションはyupを使おうと思うので、ライブラリインストールから始めることにします。

```shell
$ npm i yup

$ npm i @types/yup
```

バリデーションルールは別ファイルにまとめ、yupの書き方に則って追加していきます。
（yupの詳細は省きます）

```javascript
import { array, object, string } from "yup"

export const validationSchema = object({
  questions: array().of(
    object().shape({
      questionA: string()
        .required("必須です")
        .typeError("必須です"),
    })
  ),
})
```

`<Formik />`タグに `validationSchema`を追加していきます。

```javascript
import { validationSchema } from "./validationSchema"

export const App: React.FC<Props> = () => {
  return (
    <Formik<BaseValues>
      initialValues={initialValues}
      // 変更後
      validationSchema={validationSchema}
      onSubmit={(values, actions) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2))
          actions.setSubmitting(false)
        }, 1000)
      }}
    >
      <Form>
        ...
        <button type="submit">
          登録する
        </button>
      </Form>
    </Formik>
  )
}
```

入力エリア下にエラーメッセージが表示されるよう、コンポーネントの方も変えていきます。
このまま`<Field />`タグを使って`error`を取得するのも良いですが、今回は`useField()`を使ってエラーを取得するように実装していこうと思います。
`useField()`に関する公式サイトはこちら → [`useField()`](https://jaredpalmer.com/formik/docs/api/useField)
`useField`の()に `useField(該当フィールドのname)`を指定し、特定のフィールドのデータ（`FieldInputProps`、`FieldMetaProps`、`FieldHelperProps`）が取得できます。

```javascript
import { Field, useField } from "formik"

export const Details: React.FC<Props> = ({ fieldName }) => {
  const [field, meta] = useField(`${fieldName}.questionA`)

  return (
    <div>
      <div>Q1.</div>
      // 変更前
      <Field
        as="textarea"
        name={`${fieldName}.questionA`}
        className="textarea"
      />

      // 変更後
      <textarea {...field} />
      {meta.touched && meta.error ? (
        <div className="error">{meta.error}</div>
      ) : null}
    </div>
  )
}
```

## 感想 & まとめ
Formikは複雑な処理に向いていると思ってます。`useFormikContext()`は非常に有用です。
ですが、「ここまでのフォームじゃないんだけど」って時には、別にFormikでなくても良いと思ってます。
[`react-hook-form`](https://react-hook-form.com/jp/)や、別のライブラリ有用だと思っています。
「Formikはかゆいところに手が届きすぎる」「こんなことも出来るんだ！😲」そんな感覚です。
ぜひFormikで感動を味わってください。

## コード共有
ここまでのコードは以下の通りです。
（参考例として作ったので、あくまでも本記事のサンプル用として見てください！）

#### App.tsx

```javascript
import { Form, Formik } from "formik"
import React from "react"
import { AnimalCategory } from "./AnimalCategory"
import { Questions } from "./Questions"
import { User } from "./User"
import { validationSchema } from "./validationSchema"

export type Category = "dog" | "cat" | "rabbit"
// Formで使用する型
export type BaseValues = {
  category: Category
  email: string
  questions: Question[]
  userName: string
}

export type Question = {
  catType?: "catA" | "catB" | "catC" | ""
  dogType?: "dogA" | "dogB" | "dogC" | ""
  questionA: string
  questionB?: string
  rabbitType?: "rabbitA" | "rabbitB" | "rabbitC" | ""
}

type Props = {
  children?: never
}

const initialValues = {
  category: "dog" as Category,
  userName: "",
  email: "",
  questions: [
    {
      questionA: "",
    },
  ],
}

export const App: React.FC<Props> = () => {
  return (
    <Formik<BaseValues>
      initialValues={initialValues}
      validationSchema={validationSchema}
      onSubmit={(values, actions) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2))
          actions.setSubmitting(false)
        }, 1000)
      }}
    >
      <Form>
        <AnimalCategory />
        <User />
        <Questions />
        <button type="submit">
          登録する
        </button>
      </Form>
    </Formik>
  )
}
```

#### AnimalCategory.tsx

```javascript
import { useField, useFormikContext } from "formik"
import React from "react"
import { BaseValues, Category } from "./App"

type Props = {
  children?: never
}

export const AnimalCategory: React.FC<Props> = () => {
  const { values, setFieldValue } = useFormikContext<BaseValues>()
  const [field] = useField<Category>("category")

  const handleChange = (value: Category): void => {
    setFieldValue("category", value)
    values.questions.forEach((_q, index) => {
      switch (value) {
        case "dog":
          setFieldValue(`questions.${index}.dogType`, "")
          setFieldValue(`questions.${index}.catType`, undefined)
          setFieldValue(`questions.${index}.rabbitType`, undefined)
          break
        case "cat":
          setFieldValue(`questions.${index}.dogType`, undefined)
          setFieldValue(`questions.${index}.catType`, "")
          setFieldValue(`questions.${index}.rabbitType`, undefined)
          break
        case "rabbit":
          setFieldValue(`questions.${index}.dogType`, undefined)
          setFieldValue(`questions.${index}.catType`, undefined)
          setFieldValue(`questions.${index}.rabbitType`, "")
          break
      }
    })
  }

  return (
    <div>
      <div>カテゴリ</div>
      <select
        {...field}
        onChange={(event) => {
          handleChange(event.target.value as Category)
        }}
        onBlur={(event) => {
          handleChange(event.target.value as Category)
        }}
      >
        <option value="dog">犬</option>
        <option value="cat">猫</option>
        <option value="rabbit">うさぎ</option>
      </select>
    </div>
  )
}
```

#### User.tsx

```javascript
import React from "react"
import { Field } from "formik"

type Props = {
  children?: never
}

export const User: React.FC<Props> = () => {
  return (
    <div>
      <div>ユーザー</div>
      <Field
        type="text"
        name="userName"
        placeholder="名前"
      />
       <Field
        type="text"
        name="email"
        placeholder="Email"
      />
    </div>
  )
}
```

#### Questions.tsx

```javascript
import { FieldArray, useFormikContext } from "formik"
import React from "react"
import { BaseValues } from "./App"
import { Details } from "./Details"
import { PetType } from "./PetType"

type Props = {
  children?: never
}

export const Questions: React.FC<Props> = () => {
  const { values } = useFormikContext<BaseValues>()

  return (
    <FieldArray
      name="questions"
      render={({ remove, push }) => (
        <div>
          {values.questions.map((question, index) => (
            <div key={index}>
              <button
                onClick={() => { remove(index) }}
              >
                削除
              </button>
              <PetType fieldName={`questions.${index}`} />
              <Details fieldName={`questions.${index}`} question={question} />
            </div>
          ))}
          <button
            onClick={(e) => {
              e.preventDefault()
              push({ questionA: "" })
            }}
          >
            追加
          </button>
        </div>
      )}
    />
  )
}
```

#### PetType.tsx

```javascript
import { Field, useFormikContext } from "formik"
import React from "react"
import { BaseValues } from "./App"

type Props = {
  children?: never
  fieldName: string
}

export const PetType: React.FC<Props> = ({ fieldName }) => {
  const { values } = useFormikContext<BaseValues>()

  return (
    <div className="field is-horizontal">
      <div>タイプ</div>
      <div className="field-body">
        <div className="field control">
          {values.category === "dog" && (
            <Field as="select" name={`${fieldName}.dogType`}>
              <option value="">選択してください</option>
              <option value="dogA">dogA</option>
              <option value="dogB">dogB</option>
              <option value="dogC">dogC</option>
            </Field>
          )}
          {values.category === "cat" && (
            <Field as="select" name={`${fieldName}.catType`}>
              <option value="">選択してください</option>
              <option value="catA">catA</option>
              <option value="catB">catB</option>
              <option value="catC">catC</option>
            </Field>
          )}
          {values.category === "rabbit" && (
            <Field as="select" name={`${fieldName}.rabbitType`}>
              <option value="">選択してください</option>
              <option value="rabbitA">rabbitA</option>
              <option value="rabbitB">rabbitB</option>
              <option value="rabbitC">rabbitC</option>
            </Field>
          )}
        </div>
      </div>
    </div>
  )
}
```

#### Details.tsx

```javascript
import { Field, useField } from "formik"
import React, { Fragment } from "react"
import { Question } from "./App"

type Props = {
  children?: never
  fieldName: string
  question: Question
}

export const Details: React.FC<Props> = ({ fieldName, question }) => {
  const [field, meta] = useField(`${fieldName}.questionA`)

  return (
    <Fragment>
      <div>
        <div>Q1.</div>
        <textarea
          {...field}
          className={`textarea
          ${meta.touched && meta.error && "is-danger"}`}
        />
        {meta.touched && meta.error ? (
          <div className="help is-danger">{meta.error}</div>
        ) : null}
      {(question.dogType === "dogA" ||
        question.catType === "catA" ||
        question.rabbitType === "rabbitA") && (
        <div>
          <div>Q2.</div>
          <Field
            as="textarea"
            name={`${fieldName}.questionB`}
            placeholder="DogA, CatA, RabbitA のみ表示する入力エリア"
          />
        </div>
      )}
    </Fragment>
  )
}
```