Formikのv2が気になる！

Formikのv2.0.1-rc.13で紹介されている`useField()`が気になったので、使ってみました。
Hooksの対応が革新的そうです！

▼ Formik Releases v2.0.1-rc.13
https://github.com/jaredpalmer/formik/releases/tag/v2.0.1-rc.13

`useField()`の紹介は公式サイトを見てみてください。
[Fomik 公式 useField()](https://jaredpalmer.com/formik/docs/next/api/usefield)

## 導入
formikはTypescriptで書かれてるので、@typesライブラリのインストールは必要ありません！
Formik推奨のバリデーションライブラリ、Yupも入れておくことにします。

```shell
$ npm install formik yup @types/yup
```

それと、今回は[react-select](https://react-select.com/home)の`<Select />`タグを使いたかったので、そちらも入れておくことにします。
react-selectはセレクトボックスをいい感じに拡張してくれるライブラリです。

```shell
$ npm install react-select @types/react-select
```

## フォームの完成形
まずは完成したイメージからお見せします！アンケートフォームを作ってみた感じです。

![1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/927dcbb3-639d-b927-9103-769056f420d8.gif)

シンプルなテキストエリア、セレクトボックスを置きました。「登録する」ボタンを押した時、コンソール画面に値が取得できているか確認して見ます。

<img width="515" alt="2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/40b6167b-b52e-47eb-993c-157ec13d7ab3.png">

## 実際のコード
### `<Formik />`

フォームのコンポーネントに`<Formik />`タグを設置して、初期値（`initialValues`）や「登録」を押した時の`onSubmit`、バリデーション（`validationSchema`）を設置しています。

```javascript
import { Formik, FormikProps } from "formik"
import React from "react"
import * as Yup from "yup"

export const SampleForm: React.FC = () => {
  return (
    <div>
      <h6>アンケートフォーム</h6>
      <Formik
        initialValues={initialValues}
        validationSchema={validationSchema}
        onSubmit={(values: FormValues) => { console.log(values) }}
        render={(props: FormikProps<FormValues>) => (
          <form onSubmit={props.handleSubmit}>
            {/* 各フォーム要素 */}
            <button type="submit">登録する</button>
          </form>
        )}
      />
    </div>
  )
}
```

ちなみに、先ほどの完成形で設定した初期値は下の通りです。

```javascript
const initialValues: FormValues = {
  nickname: "",
  experience: true,
  message: "",
  rabbits: [],
  favoriteFoods: [],
  petName: [],
}
```

今回はバリデーションに付いて触れませんが、`validationSchema`の書き方は公式サイトがとても分かりやすいです！ → [Formik公式 Validation](https://jaredpalmer.com/formik/docs/next/guides/validation)

### `<input type="text" />`
ニックネームとメッセージの入力エリアは、それぞれコンポーネントを分けて作りました。
`<input />`タグ用の`<TextField />`コンポーネントに`name`だけ渡しています。

```javascript
<Formik
  render={(props: FormikProps<FormValues>) => (
    ...
    <TextField name="nickname" />
    ...
```

下が`<TextField />`コンポーネントです。公式サイト通りですが、`useField()`を下のように使っています。

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

type Props = {
  name: string
}

export const TextField: React.FC<Props> = ({ name, ...props }) => {
  const [field, meta] = useField(name)

  return (
    <div>
      <input {...field} {...props} />
      {meta.error && meta.touched && <small>{meta.error}</small>}
    </div>
  )
}
```

今回は`<Field />`タグは使いませんでしたが、
Formik v2では、下のような`<Field />`タグで`component`を指定するのは非推奨らしいです。

```javascript
// 非推奨らしい
<Field name="firstName" component={CustomInputComponent} />
  ↓
<Field name="firstName" as={CustomInputComponent} />
```

### react-selectを使った`<Select />`

```javascript
<Formik
  render={(props: FormikProps<FormValues>) => (
    ...
    <RabbitSelect
      name="rabbits"
      options={rabbitOptions}
      placeholder="1種類選択してください"
    />
    ...
```

`options`で渡しているのは、`label`と`value`で値をセットした配列です。

```javascript
/**
 * うさぎの選択肢
 */
const rabbitOptions = [
  {
    label: "ネザーランド・ドワーフ",
    value: "netherland-dwarf"
  },
  {
    label: "ホーランド・ロップ",
    value: "holland-lop"
  },
  ...
]
```

`<RabbitSelect />`コンポーネントは下のように実装しました。
`setFieldValue()`も使いたかったので、`useField()`と合わせて[`useFormikContext()`](https://jaredpalmer.com/formik/docs/next/api/useformikcontext)を使っています。
（`as never`は妥協です。。あまり参考になさらないでください。）

```javascript
import React from "react"
import Select from "react-select"
import { ValueType } from "react-select/src/types"
import { useFormikContext, useField } from "formik"

type OptionType = {
  label: string
  value: string
}

type Props = {
  name: string
  options: OptionType[]
  placeholder?: string
}

export const RabbitSelect: React.FC<Props> = ({
  name,
  options,
  placeholder,
}) => {
  const [field] = useField(name)
  const { setFieldValue } = useFormikContext()

  const onChange = React.useCallback((option) => {
    setFieldValue(
      field.name as never,
      (option as OptionType).value
    )
  }, [field.name, setFieldValue])

  const getValue = (): ValueType<OptionType> => {
    if (options) {
      return options.find(option => option.value === field.value)
    } else {
      return "" as any
    }
  }

  return (
    <Select
      name={field.name}
      value={getValue()}
      onChange={onChange}
      placeholder={placeholder}
      options={options}
    />
  )
}
```

今回はreact-selectについて触れませんが、複数選択できるのは`<Select />`に`isMulti`を渡すだけです！
上の`<RabbitSelect />`と似たような記述です。

```javascript
export const FoodSelect: React.FC<Props> = ({
  ... // 上と同じ
}) => {
  ... // 上と同じ
  const onChange = React.useCallback((option) => {
    setFieldValue(
      field.name as never,
      (option as OptionType[]).map((item) => item.value)
    )
  }, [field.name, setFieldValue])

  const getValue = (): ValueType<OptionType> => {
    if (options) {
      return options.filter(option => field.value.indexOf(option.value) >= 0)
    } else {
      return []
    }
  }

  return (
    <Select
      name={field.name}
      value={getValue()}
      onChange={onChange}
      placeholder={placeholder}
      options={options}
      isMulti
    />
  )
}
```

## 最後に
すみません、あまり`useField()`の詳細について触れておりません。。とりあえず実装してみたかったので今回は備忘録がてら記事にさせて頂きました！まだ試していくので、都度更新していくことにします。