64
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Formikが使いたい!

Last updated at Posted at 2019-04-26

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

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

Youtbe: Better React Forms with Formik

2020/02/26 追記
Formikの別記事を書きました。 → Formikを導入する

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

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

▼ 出来上がり
output.gif

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

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」も一緒にインストールしてしまいます。

$ npm install formik yup --save

##Youtubeを見ながら実装

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

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

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

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

ここに書いていきます

export default FormikForm

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

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.titlethis.state.body使っちゃってますので、エラーが出るはずです。早速Formikタグを使っていきましょう!

###①withFormik
FormikForm.jsでFormikが提供しているwithFormikを使っていきます。
まずは上で呼び出して、

import { withFormik } from 'formik'
const FormikForm = withFormik({

})(Form)

export default FormikForm

こんな感じ。波括弧内に、propsでtitle、bodyの初期値を設定するため、mapPropsToValuesを使っていきます。今回ダミーでtest1とか入れてみました。

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

###②Formik props: values

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

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

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

1.png

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

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

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

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

###④handleChangehandleSubmit

Form.jsにonChangeonSubmitイベントに関数をセットします。

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は下のように定義されています。

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

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

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

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

2.png

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

###⑤<Form /><Field />

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

import { Form,  } from 'formik'

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

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を使っていくことになります。
最初にwithFormikvalidationSchemaをセットしていきます。

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

  • required
  • max

を実装していきます。

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の値}です。

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

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

###⑧touched

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

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

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.titletouched.bodyがそれぞれtrueの時」をそれぞれ追加してあげることで、onBlur時のバリデーションチェックのような挙動になってくれます。

output4.gif

いい感じー!!🤗

###⑨handleSubmitに実際の挙動を追加

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

▼ App.js

...
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

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

export default FormikForm

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

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

##【感想】
恐らくこのQiitaを読むよりYoutubeを観た方が理解が早いかも笑 Youtubeではチェックボックスやパスワードの場合も紹介していました。
正直公式ドキュメントは掻い摘んで(google翻訳しながら笑)見ただけなので、もっと実践寄りのFormik特集もやっていけたらと思っています。

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

64
47
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
64
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?