🐥

ReactのフォームライブラリでFormikをやめてreact-hook-formを使いたい

2022/09/07に公開

概要

業務でFormikを使ってフォームを何個か作っていたんですが、
Formikはパフォーマンスが悪い、react-hook-formを使うべきといった記事を見かけたので
実際に試してみたかったので、ついでに 使ってみた の記事も書きました。

npmトレンドを見ると、どちらもシェアを伸ばしてますが、
react-hook-form の方が人気になってきているようです。
https://npmtrends.com/formik-vs-react-final-form-vs-react-hook-form

Formik

まずはFormikから使ってみます。

インストール

yarn add formik yup
yarn add -D @types/yup
formikとバリデーションスキーマ定義用のyupをインストール

コードを書いてみる

bbbccc の 文字数と必須入力をチェックするフォームです。
フォームのレンダリングの回数を見たかったので、 console.log('renderd') を置いてます。

const initialValues = {
  aaa: '',
  bbb: '',
  ccc: '',
}

const formValidationSchema = yup.object().shape({
  aaa: yup.string(),
  bbb: yup.string().required('入力は必須です'),
  ccc: yup.string().max(5, '5文字以内で入力してください').required('入力は必須です'),
})

const FormikTestForm = () => {
  const handleFormSubmit = (data) => {}

  const { values, errors, touched, handleBlur, handleChange, handleSubmit } =
    useFormik({
      onSubmit: handleFormSubmit,
      initialValues,
      validationSchema: formValidationSchema,
    })

  console.log('renderd') //レンダリングの確認用

  return (
    <form onSubmit={handleSubmit}>
      Formik
      <div className="mb-4">
        <input
          name="aaa"
          className="p-2"
          placeholder="aaa"
          type="text"
          onBlur={handleBlur}
          onChange={handleChange}
          value={values.aaa}
        />
      </div>
      <div className="mb-4">
        <input
          className="p-2"
          type="text"
          placeholder="bbb"
          name="bbb"
          onBlur={handleBlur}
          onChange={handleChange}
          value={values.bbb}
        />
        {touched.bbb && errors.bbb && (
          <p className="text-red-400">{errors.bbb}</p>
        )}
      </div>
      <div className="mb-4">
        <input
          name="ccc"
          className="p-2"
          placeholder="ccc"
          value={values.ccc}
          onBlur={handleBlur}
          onChange={handleChange}
        />
        {touched.ccc && errors.ccc && (
          <p className="text-red-400">{errors.ccc}</p>
        )}
      </div>
      <button
        type="submit"
        className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
      >
        送信
      </button>
    </form>
  )
}

画面イメージ

各inputへの入力やフォーカスが外れる度に
フォーム全体が複数回再レンダリングされています。
確かにパフォーマンスが悪そうです。

react-hook-form

今度は react-hook-form を使ってみます。

インストール

yarn add react-hook-form
react-hook-formをインストール

バリデースキーマを定義したかったら下記もインストール
@hookform/resolvers

コードを書いてみる

公式のサンプルを参考に、とりあえずの簡単なフォームを作成しました。
上記のFormikで作ったフォームと同じく、bbbccc の 文字数と必須入力をチェックするフォームです。
レンダリングの回数を見たかったので、 console.log('renderd') を置いてます。

type Inputs = {
  aaa: string
  bbb: string
  ccc: string
}

const ReactHookFormTestForm = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Inputs>()

  const onSubmit = (data) => {
    console.log(data)
  }

  console.log('renderd') //レンダリングの確認用

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      React-Hook-Form
      <div className="mb-4">
        <input
          className="p-2"
          type="text"
          placeholder="aaa"
          {...register('aaa')}
        />
      </div>
      <div className="mb-4">
        <input
          className="p-2"
          type="text"
          placeholder="bbb"
          {...register('bbb', { required: true })}
        />
        {errors.bbb && <p className="text-red-400">入力は必須です。</p>}
      </div>
      <div className="mb-4">
        <input
          id="name"
          className="p-2"
          placeholder="ccc"
          {...register('ccc', { required: true, maxLength: 5 })}
        />
        {errors.ccc && errors.ccc.type === 'required' && (
          <p className="text-red-400">入力は必須です</p>
        )}
        {errors.ccc && errors.ccc.type === 'maxLength' && (
          <p className="text-red-400">5文字以内で入力してください。</p>
        )}
      </div>
      <button
        type="submit"
        className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
      >
        送信
      </button>
    </form>
  )     

画面イメージ

作成したフォームを使ってみました。

入力内容にエラーがあった時と、再入力でエラーが解消されたときにだけ再レンダリングしていますね。
必要最小限のレンダリングで、非常に効率が良さそうです。

補足

react-hook-formはデフォルトでは、フォームがsubmitされた後にバリデーションのチェックを開始します。
Formik

react-hook-form

useFormフックは引数に複数のoptionを渡せるのですが、その中で modeオプションを
onChangeにするとFormikと同じように submit される前でもバリデーションのチェックを行うことができるみたいです。

useForm({
    mode: 'onChange',
})

公式ではパフォーマンスに影響があるので注意、と書かれていますが、
それでもFormikと比べるとパフォーマンスは良さそうですね。

終わりに

ブラウザの perfomance タブなどでのちゃんとした検証ではないが、
巨大なフォームだったり、底辺の端末とかだと、より違いが分かるかもしれない。
現状は新規で導入する場合、 よりパフォーマンスが良いreact-hook-form を選ぶべき。

参考

https://formik.org/
https://react-hook-form.com/
日本語版
https://react-hook-form.com/jp/

Discussion