Formikが使いたい！

React.jsのバリデーションライブラリ「Formik」がかなり人気を博しているようなので、早速勉強することにしました！

今回参考にさせて頂いた動画＆記事はこちらです♫

Youtbe: [Better React Forms with Formik](https://www.youtube.com/watch?v=yNiJkjEwmpw&t=445s)

**2020/02/26 追記**
Formikの別記事を書きました。 → [Formikを導入する](https://qiita.com/Hitomi_Nagano/items/424ef83dba427bd0b2b8)

##何もライブラリを使用しなかったときのコード

シンプルですが記事タイトルと本文に入力文字数に関するバリデーションを設けたフォームを作成してみました。

▼ 出来上がり
![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/3dfe9c74-3a72-6fd6-8318-988582e82ea5.gif)

▼ ソースコード
元になるソースコードも載せます。
あえて、かなり冗長的に作ってます。

```javascript
import React, { Component } from 'react'

class Form extends Component {
  constructor(props) {
    super(props);
    this.state = {
      title: '',
      errorTitle: '',
      validTitle: true,
      body: '',
      errorBody: '',
      validBody: true,
    }
  }

  handleChangeTitle = e => {
    const value = e.target.value
    this.setState({
      title: value,
      errorTitle: ''
    })

    if (0 < value.length &&  value.length <= 100) {
      this.setState({ validTitle: false })
    } else {
      this.setState({ validTitle: true })
    }
  }

  handleBlurTitle = e => {
    const value = e.target.value
    if (value.length === 0) {
      this.setState({ errorTitle: '記事タイトルを入力してください' })
    } else if (value.length > 100) {
      this.setState({ errorTitle: '記事タイトルは100文字以内で入力してください' })
    } else {
      this.setState({ errorTitle: '' })
    }
  }

  handleChangeBody = e => {
    const value = e.target.value
    this.setState({
      body: value,
      errorBody: ''
    })

    if (0 < value.length && value.length <= 200) {
      this.setState({ validBody: false })
    } else {
      this.setState({ validBody: true })
    }
  }

  handleBlurBody = e => {
    const value = e.target.value
    if (value.length === 0) {
      this.setState({ errorBody: '記事本文を入力してください' })
    } else if (value.length > 200) {
      this.setState({ errorBody: '記事本文は200文字以内で入力してください' })
    } else {
      this.setState({ errorBody: '' })
    }
  }

  handleSubmit = e => {
    e.preventDefault()
    const { title, body, validTitle, validBody } = this.state

    if (validTitle === false && validBody === false) {
      this.props.addPost({ title, body })

      this.setState({
        title: '',
        body: '',
        validTitle: true,
        validBody: true
      })
    } else {
      if (title.length === 0) {
        this.setState({ errorTitle: '記事タイトルを入力してください' })
      } else if (title.length > 100) {
        this.setState({ errorTitle: '記事タイトルは100文字以内で入力してください' })
      } else {
        this.setState({ errorTitle: '' })
      }

      if (body.length === 0) {
        this.setState({ errorBody: '記事本文を入力してください' })
      } else if (body.length > 200) {
        this.setState({ errorBody: '記事本文は200文字以内で入力してください' })
      } else {
        this.setState({ errorBody: '' })
      }
    }
  }

  render() {
    return (
      <div>
        <h5>記事を投稿</h5>
        <form onSubmit={this.handleSubmit}>
          <div>
            <label htmlFor="title">記事タイトル</label>
            <input
              value={this.state.title}
              type="text"
              name="title"
              onChange={this.handleChangeTitle}
              onBlur={this.handleBlurTitle}
            />
            <div>{this.state.errorTitle}</div>
          </div>
          <div>
            <label htmlFor="body">本文</label>
            <textarea
              value={this.state.body}
              name="body"
              onChange={this.handleChangeBody}
              onBlur={this.handleBlurBody}
              rows="5"
            />
            <div>{this.state.errorBody}</div>
          </div>
          <button type="submit">
            投稿する
          </button>
        </form>
      </div>
    )
  }
}

export default Form
```

………なげぇぇぇ〜〜〜〜っっ！！！😲😲😲

早速Formikを使っていきます！

##Formikを使う前準備

ライブラリのインストールからしていきます。formik公式が推奨しているバリデーションライブラリ、「yup」も一緒にインストールしてしまいます。

```shell
$ npm install formik yup --save
```

##Youtubeを見ながら実装

Youtube通りやったらかなり長い説明書になってしまいました😅

###⓪Formik用コンポーネントを作成＆Form.jsを関数コンポーネントに書き換え

`FormikForm.js`ファイルを新規作成して、Form.jsをラップするコンポーネントを作成しました。

```javascript
import React from 'react'
import Form from './Form'
import * as yup from 'yup'

ここに書いていきます

export default FormikForm
```

`Form.js`はclass構文で作っていましたが、今まで書いたstateやonChange、onBlurイベントを全部取っ払って関数コンポーネントにしました。

```javascript
import React from 'react'
import { connect } from 'react-redux'
import { addPost } from '../actions'

const Form = () => {
  return (
    <div>
      <h5>記事を投稿</h5>
      <form>
        <div>
          <label htmlFor="title">記事タイトル</label>
          <input
            value={this.state.title}
            type="text"
            name="title"
          />
        </div>
        <div>
          <label htmlFor="body">本文</label>
          <textarea
            value={this.state.body}
            name="body"
            rows="5"
          />
        </div>
        <button type="submit">投稿する</button>
      </form>
    </div>
  )
}

export default connect(null, { addPost })(Form);
```

もちろん上では`this.state.title`、`this.state.body`使っちゃってますので、エラーが出るはずです。早速Formikタグを使っていきましょう！


###①withFormik
FormikForm.jsでFormikが提供している[`withFormik`](https://jaredpalmer.com/formik/docs/api/withformik)を使っていきます。
まずは上で呼び出して、

```javascript
import { withFormik } from 'formik'
```

```javascript
const FormikForm = withFormik({

})(Form)

export default FormikForm
```

こんな感じ。波括弧内に、propsでtitle、bodyの初期値を設定するため、[`mapPropsToValues`](https://jaredpalmer.com/formik/docs/api/withformik#mappropstovalues-props-props-values)を使っていきます。今回ダミーで`test1`とか入れてみました。

```javascript
const FormikForm = withFormik({
  mapPropsToValues() {
    return {
      title: 'test1',
      body: 'test2'
    }
  }
})(Form)
```

###②Formik props: values

お次に先ほど指定した初期値がレンダーされるよう、Form.jsに`values`を指定していきます。

```javascript
const Form = ({
  values
}) => {
  return (
    ...
    <input
      value={values.title}
      type="text"
      name="title"
    />
    <textarea
      value={values.body}
      name="body"
      rows="5"
    />
    ...
  )
}
```

保存したら初期値が表示されました！

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/51d9c9e0-7778-4ef9-69af-96b9d63ba6b8.png)

###③初期値をpropsで渡す

初期値が空でなく、指定したデータを表示したい場合、`<FormikForm title="デフォルトで表示した記事タイトル" body="デフォルトで表示したい記事本文" />`みたいに、propsで渡してあげることが出来るようです。
FormikForm.jsで直入力していた初期値を、変数名`title`、`body`をセットしましょう。

```javascript
const FormikForm = withFormik({
  mapPropsToValues({ title, body }) {
    return {
      title: title || '', // titleに値が入っていたらpropsで渡されたtitleを表示、または空表示
      body: body || ''
    }
  }
})(Form)
```

表示が問題ないことが確認できます。

###④[`handleChange`](https://jaredpalmer.com/formik/docs/api/formik#handlechange-e-reactchangeevent-any-void)、[`handleSubmit`](https://jaredpalmer.com/formik/docs/api/withformik#handlesubmit-values-values-formikbag-formikbag-void)

Form.jsに`onChange`と`onSubmit`イベントに関数をセットします。

```javascript
const Form = ({
  values,
  handleChange,
  handleSubmit
}) => {
  return (
    ...
    <form onSubmit={handleSubmit}>
      <input
        value={values.title}
        type="text"
        name="title"
        onChange={handleChange}
      />
      <textarea
        value={values.body}
        name="body"
        rows="5"
        onChange={handleChange}
      />
    ...
  )
}
```

公式サイトで`handleSubmit`は下のように定義されています。

```javascript
handleSubmit: (values: Values, formikBag: FormikBag) => void
```

FormikForm.jsに`handleSubmit`で引数の`values`が取れるかどうか、console.logで確認することにします。

```javascript
const FormikForm = withFormik({
  mapPropsToValues({ title, body }){
    ...
  },
  handleSubmit(values){
    console.log("values", values)
  }
})(Form)
```

てきとうに値を入力して送信をクリックしたら、ちゃんとconsole.logで`title`と`body`が出力してくれました！

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/20a2305c-6e94-d07a-00ff-a44748ebdf1d.png)

ここまでで、バリデーションの無いフォームの挙動を実装できました。

###⑤`<Form />`、[`<Field />`](https://jaredpalmer.com/formik/docs/api/field)

`onSubmit`を実装しましたが、formタグをformikが提供している`<Form />`に書き換えると省略することができます。
同様に`input`タグも`<Field />`タグに書き換えることで`onChange`を省くことが出来ます。

```javascript
import { Form,  } from 'formik'
```

…ここで予期しないことが起こりました。。
Form.jsでコンポーネントを作成してきましたが、Formikの`Form`を使うとなると、コンポーネント名を変える必要があります。。ここまでForm.jsで進めてきましたが、コンポーネント名を変えます。


```javascript
const PostForm = ({
  values // ↓ handleSubmit、handleChangeを削除
}) => {
  return (
    ...
    <Form>
      <Field
        value={values.title}
        name="title"
      />
      <Field
        value={values.body}
        name="body"
        rows="5"
        component="textarea"
      />
    </Form>
  )
}
```

変わらずSubmitイベントが走ることを確認します。

###⑥バリデーションを作成

ここからバリデーションライブラリ[`Yup`](https://github.com/jquense/yup)を使っていくことになります。
最初に`withFormik`の[`validationSchema`](https://jaredpalmer.com/formik/docs/api/withformik#validationschema-schema-props-props-schema)をセットしていきます。


今回は「必須」と「最大文字数」を必須としたシンプルなアプリケーションなので、

* `required`
* `max`

を実装していきます。

```javascript
const FormikForm = withFormik({
  ...
  validationSchema: yup.object().shape({
    title: yup.string().max(100, 'タイトルは100文字以内で入力してください').required('タイトルは必須項目です'),
    body: yup.string().max(200, '本文は200文字以内で入力してください').required('本文は必須項目です')
  }),
  ...
})(PostForm)
```

`yup.object().shape({ ... })`のオブジェクト内にそれぞれのバリデーションの条件とエラーメッセージをセットで指定していくようです。


###⑦エラーメッセージを表示

PostForm.jsで`errors`をpropsで渡してエラーメッセージを表示するようにします。
エラーメッセージは`{errors.インプットタグのnameの値}`です。

```javascript
const PostForm = ({
  values,
  errors // 追加
}) => {
  return (
    ...
    <Field
      value={values.title}
      name="title"
    />
    {errors.title && <div className="errorText">{errors.title}</div>}

    <Field
      value={values.body}
      name="body"
      rows="5"
      component="textarea"
    />
    {errors.body && <div className="errorText">{errors.body}</div>}
    ...
  )
}
```

「ここまで実装したら大丈夫だろう」と思ったのですが、もう一踏ん張りのようです。
上記のコードだとこんな感じです。

![output3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/0cc9c501-bf02-21f9-f013-694c1a657a68.gif)

なぜか入力していない本文のエラーメッセージまで表示されてしまいました。

###⑧`touched`

入力エリアにカーソルがクリックされたかどうかを、`touched`が`true/false`で判断してくれるのだそうな。

PostForm.jsに`touched`もpropsで渡していきます。

```javascript
const PostForm = ({
  values,
  errors,
  touched // 追加
}) => {
  return (
    ...
    {touched.title && errors.title && <div className="errorText">{errors.title}</div>}
    ...
    {touched.body && errors.body && <div className="errorText">{errors.body}</div>}
    ...
  )
}
```

「`touched.title`、`touched.body`がそれぞれtrueの時」をそれぞれ追加してあげることで、onBlur時のバリデーションチェックのような挙動になってくれます。

![output4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/b1e5e24d-ad06-504d-afdb-db485c7cba4b.gif)

いい感じー！！🤗


###⑨[`handleSubmit`](https://jaredpalmer.com/formik/docs/api/withformik#handlesubmit-values-values-formikbag-formikbag-void)に実際の挙動を追加

すっかりすっかり忘れてましたが、`handleSubmit`にReduxのActionで作成した「記事を追加する」関数を実行せねばならなかったのでした。。
`console.log`としていたところを一回削除して、`this.props.addPost`をForm側のコンポーネントに渡してあげたいと思います。
FormFormikコンポーネントの親コンポーネントにAction Creatorsを呼び出すconnectメソッドを実装し、
`<FormikForm handleSubmit={props.addPost} />`のように`handleSubmit`関数を定義しちゃいました。

▼ App.js

```javascript
...
import { connect } from 'react-redux'
import { addPost } from '../actions'

const App = (props) => {
  return (
    <div>
      <FormikForm handleSubmit={props.addPost} />
      <Posts />
    </div>
  )
}

export default connect(null, { addPost })(App)
```

▼ FormFormik.js

```javascript
const FormikForm = withFormik({
  ...
  handleSubmit: (payload, { props, resetForm, setSubmitting }) => {
    props.handleSubmit(payload) // 入力値の値がpayloadに入り、`props.handleSubmit`としてdispatchしています
    setSubmitting(false) // これは何だろな？
    resetForm() // 入力、送信後に入力エリアを空にする
  }
})(PostForm)

export default FormikForm
```


##出来上がったソースコード

完成のgithub URLを記載します。

https://github.com/Hitomi-Nagano/first_formik_qiita


##【感想】
恐らくこのQiitaを読むより[Youtube](https://www.youtube.com/watch?v=yNiJkjEwmpw&t=445s)を観た方が理解が早いかも笑 Youtubeではチェックボックスやパスワードの場合も紹介していました。
正直公式ドキュメントは掻い摘んで（google翻訳しながら笑）見ただけなので、もっと実践寄りのFormik特集もやっていけたらと思っています。

Formikを突き詰めていきたいところですが…！
react-test-rendererについて理解を深めたいので、次回、テストについてQiitaの記事を書こうと思います。✍️（宣言しないと先延ばししそうなのであえて次記事宣言させて頂きました。😅）