React Hook Formを使用したバリデート付きフォーム作成術

React Hook Formとは

React Hook FormはReactアプリケーションでフォームを簡単に扱うためのライブラリです。

React Hook Formを使うには、まずReactが使える開発環境を作成して、react-hook-formをインストールします。

ShellScript
npm create vite@latest my-react-form -- --template react-swc-ts

フォームの見た目を良くするために、bulmaもインストールしておきます。

ShellScript
cd my-react-form
npm install
npm i -D react-hook-form bulma

App.cssにbulma.cssをimportします。

App.css
@import "bulma/css/bulma.css";

main.tsxの「import './index.css'」は使わないので削除します。

main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

React Hook Form テキストの入力

App.tsxを以下のコードに変更します。

main.tsx
import './App.css'
import { useForm } from 'react-hook-form'

type FormData = {
  name: string
}

export default function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({ mode: 'onTouched' })

  const onSubmit = handleSubmit((data) =>
    alert(`名前:${data.name}`)
  )

  return (
    <section className="section">
      <div className="container is-max-desktop">
        <form className="box" onSubmit={onSubmit}>
          <div className="field">
            <label className="label">名前</label>
            <input
              className="input"
              type="text"
              {...register("name", {
                required: "テキストを入力してください",
                minLength: {
                  value: 2,
                  message: "名前は2文字以上で入力してください",
                },
                maxLength: {
                  value: 10,
                  message: "名前は10文字以下で入力してください",
                },
              })}
            />
            {errors.name && (
              <p className="help is-danger">{errors.name.message}</p>
            )}
          </div>
          <div className="field">
            <button type="submit" className="button is-primary">
              送信する
            </button>
          </div>
        </form>
      </div>
    </section>
  )
}

React Hook Form テキストの入力 サンプル

コード内の「mode: 'onTouched'」は最初はblurイベントでトリガーして、その後はchangeイベントでトリガーされます。

ユーザビリティ面を考慮した場合、フォームのバリデートは「mode: 'onTouched'」にしたほうが使いやすいです。

modeを設定しないと、submitボタンを押してからバリデートを開始する設定になってしまうので、modeは必ず設定してください。

mode: 'onTouched'

React Hook Form 数値入力

数値の入力の場合は以下のようになります。

main.tsx
import './App.css'
import { useForm } from 'react-hook-form'

type FormData = {
  age: number
}

export default function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({ mode: 'onTouched' })

  const onSubmit = handleSubmit((data) =>
    alert(`年齢:${data.age}`)
  )

  return (
    <section className="section">
      <div className="container is-max-desktop">
        <form className="box" onSubmit={onSubmit}>
          <div className="field">
          <label className="label">年齢</label>
            <input
              className="input"
              type="number"
              {...register("age", {
                required: "年齢を入力してください",
                min: {
                  value: 18,
                  message: "18歳以上で入力してください",
                },
                max: {
                  value: 120,
                  message: "120歳以下で入力してください",
                },
                valueAsNumber: true,
              })}
            />
            {errors.age && (
              <p className="help is-danger">{errors.age.message}</p>
            )}
          </div>
          <div className="field">
            <button type="submit" className="button is-primary">
              送信する
            </button>
          </div>
        </form>
      </div>
    </section>
  )
}

React Hook Form 数値入力サンプル

React Hook Form 正規表現チェック

入力部分に正規表現のバリデートを追加したい場合はpatternを使用します。

TSX
import './App.css'
import { useForm } from 'react-hook-form'

type FormData = {
  email: string
}

export default function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({ mode: 'onTouched' })

  const onSubmit = handleSubmit((data) =>
    alert(`メールアドレス:${data.email}`)
  )

  return (
    <section className="section">
      <div className="container is-max-desktop">
        <form className="box" onSubmit={onSubmit}>
          <div className="field">
            <label className="label">メールアドレス</label>
            <input
              className="input"
              type="email"
              {...register("email", {
                required: "メールアドレスを入力してください",
                pattern: {
                  value: /.+@.+/,
                  message: "メールアドレスの入力形式が正しくないです",
                },
              })}
            />
            {errors.email && (
              <p className="help is-danger">{errors.email.message}</p>
            )}
          </div>
          <div className="field">
            <button type="submit" className="button is-primary">
              送信する
            </button>
          </div>
        </form>
      </div>
    </section>
  )
}

React Hook Form 正規表現サンプル

Zodでスキーマバリデーション

React Hook Formはスキーマバリデーションに対応しているので、Zodを使用すればより簡単にバリデートの設定ができます。

設定するには、まずZodと@hookform/resolversをインストールします。

ShellScript
npm i -D zod @hookform/resolvers

インストールしたらApp.tsxを以下のZodを使用したコード貼り付けてください。

前述のemailのバリデートをZodを使用したコードに書き換えています。

TSX
import './App.css'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'

type FormData = {
  email: string
}

const schema = z.object({
  email: z
    .string()
    .trim()
    .min(1, { message: 'メールアドレスを入力してください' })
    .email({ message: 'メールアドレスの入力形式が正しくないです' })
})

export default function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({
    mode: 'onTouched',
    resolver: zodResolver(schema),
  })

  const onSubmit = handleSubmit((data) =>
    alert(`メールアドレス:${data.email}`)
  )

  return (
    <section className="section">
      <div className="container is-max-desktop">
        <form className="box" onSubmit={onSubmit}>
          <div className="field">
            <label className="label">メールアドレス</label>
            <input
              className="input"
              type="email"
              {...register("email")}
            />
            {errors.email && (
              <p className="help is-danger">{errors.email.message}</p>
            )}
          </div>
          <div className="field">
            <button type="submit" className="button is-primary">
              送信する
            </button>
          </div>
        </form>
      </div>
    </section>
  )
}

Zodでスキーマバリデーションのサンプル

Zodの使い方は公式サイトを参照してください。

z.string()のようにメソッドをつなげる書き方なので、簡単に設定できます。

Zod使用前より使用後のほうがコードが「…register」部分に条件を書かなくて良いので、コードがだいぶスッキリして見やすくなります。

特に理由がなければ、React Hook Formを使用する際はZodを併用することをオススメします。