LoginSignup
36
17

More than 5 years have passed since last update.

Formikを使う

Last updated at Posted at 2018-04-15

Formikを使う

jaredpalmer/formik: Build forms in React, without the tears 😭

Formikはいくつかの点でRedux-Formより優れている。

readmeによると、

Redux-Formでないのかというのと、フォームの状態は本質的に一過性で局所的であるためにreduxでそれを追跡することは不要ため。
さらにredux-fromの場合必ずreducerを通るためアプリケーションの成長につれて描画コストが上がる。
またサイズも半分程度である。
ということがreadmeに書いてある。

まあだいたい同意で、ただのReactコンポーネントでしかのでテストも書きやすい。

render prop

HoCよりrender propの方がたいていの場合好ましいので、render propで書いていく。

function MyForm() {
  return (
    <div>
      <h1>My Form</h1>
      <Formik
        initialValues={{
          email: '',
          password: ''
        }}
        validate={values => {
          let errors = {}
          if (!values.email) {
            errors.email = 'Required'
          } else if (
            !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)
          ) {
            errors.email = 'Invalid email address'
          }
          return errors
        }}
        onSubmit={(values, { setSubmitting, setErrors }) => {
          LoginToMyApp(values).then(
            user => {
              setSubmitting(false)
            },
            errors => {
              setSubmitting(false)
              setErrors(transformMyApiErrors(errors))
            }
          )
        }}
        render={({
          values,
          errors,
          touched,
          handleChange,
          handleBlur,
          handleSubmit,
          isSubmitting
        }) => (
          <form onSubmit={handleSubmit}>
            <input
              type="email"
              name="email"
              onChange={handleChange}
              onBlur={handleBlur}
              value={values.email}
            />
            {touched.email && errors.email && <div>{errors.email}</div>}
            <input
              type="password"
              name="password"
              onChange={handleChange}
              onBlur={handleBlur}
              value={values.password}
            />
            {touched.password &&
              errors.password && <div>{errors.password}</div>}
            <button type="submit" disabled={isSubmitting}>
              Submit
            </button>
          </form>
        )}
      />
    </div>
  )
}

ただ、これだと冗長な部分が多い。
Fieldコンポーネントを使うと冗長性をなくせる。
ついでに、validateも外出した方が見通しがよくなる。
また、yupと連携できるが、デフォルトのバリデーションの挙動が非同期になるのでテストでは注意。

function validate(values) {
  let errors = {}
  if (!values.email) {
    errors.email = 'Required'
  } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
    errors.email = 'Invalid email address'
  }
  if (!values.password) {
    errors.password = 'Required'
  }
  return errors
}

function MyForm2() {
  return (
    <div>
      <h1>My Form2</h1>
      <Formik
        initialValues={{ email: '', password: '' }}
        validate={validate}
        onSubmit={(values, { setSubmitting, setErrors }) => {
          LoginToMyApp(values).then(
            user => {
              setSubmitting(false)
            },
            errors => {
              setSubmitting(false)
              setErrors(transformMyApiErrors(errors))
            }
          )
        }}
        render={({ values, errors, touched, handleSubmit, isSubmitting }) => (
          <form onSubmit={handleSubmit}>
            <Field type="email" name="email" placeholder="Email" />
            {touched.email && errors.email && <div>{errors.email}</div>}
            <Field type="password" name="password" placeholder="Password" />
            {touched.password &&
              errors.password && <div>{errors.password}</div>}
            <button type="submit" disabled={isSubmitting}>
              Submit
            </button>
          </form>
        )}
      />
    </div>
  )
}

また、エラーメッセージの部分が冗長なので、readmeにあるように自分でErrorMessageコンポーネントを定義するとよい。

import { Field, getIn } from 'formik';

const ErrorMessage = ({ name }) => (
  <Field
    name={name}
    render={({ form }) => {
      const error = getIn(form.errors, name);
      const touch = getIn(form.touched, name);
      return touch && error ? error : null;
    }}
  />
);

これを使うと非常にスッキリする。

function MyForm3() {
  return (
    <div>
      <h1>My Form3</h1>
      <Formik
        initialValues={{ email: '', password: '' }}
        validate={validate}
        onSubmit={submit}
        render={({ handleSubmit, isSubmitting }) => (
          <form onSubmit={handleSubmit}>
            <Field type="email" name="email" placeholder="Email" />
            <ErrorMessage name="email" />
            <Field type="password" name="password" placeholder="Password" />
            <ErrorMessage name="password" />
            <button type="submit" disabled={isSubmitting}>
              Submit
            </button>
          </form>
        )}
      />
    </div>
  )
}

テストは別記事に書く予定。

バリデーション

デフォルトでyupと連携しているが、このバリデーションライブラリにはテストがないのでどこまで信用していいのか判断が微妙なところ。
またデフォルトで英語のロケーションが入るのでバリデーションについては検討の余地がある。

36
17
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
36
17