React Hook Formの基本まとめ その1(React Hook Formのみ使用するパターン)

こんにちは! 今回はReact Hook Formについてまとめます。

React Hook Formとは

  • Reactのフォームライブラリ
  • 簡潔な記述でコード量を減らしながら、レンダリングを抑えることが出来るのでパフォーマンスも向上する
  • 以下RHFと略す

RHFのみ使用する場合

  • まずは一番シンプルなRHFのみ使用したパターンから説明

サンプルコード

import { useForm } from 'react-hook-form'

function app() => {
  const { register, handleSubmit, formState: { errors } }  = useForm()

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name', { required: 'このフィールドは必須です' })} />
       {errors.name && <p>{ errors.name.message }</p>}
       <button type="submit">送信</button>
    </form>
  )
}

解説

useForm

  • RHFを使うためのフック
  • ここから必要なメソッドや状態を取得する

register

  • RHFでフォームを管理するためのメソッドや状態が含まれているオブジェクト
  • registerの中身をinputタグなどにスプレッド構文ですべて付与することで、付与されたinputタグがRHFの管理下に置かれる

handleSubmit

  • RHFで管理されたフォームをsubmitする時に使用する
  • フォームの値を集約してonSubmitにオブジェクトとして渡す

formState

  • フォームの状態を保持するオブジェクト
  • formState: { errors } とすることで、よく使用するerrorsにアクセスしやすいように、 errors 単体で変数として取り出している

ポイント

...register について

  • RHFのよくわからないポイントだった ...registerについて苦手を解消する
  • まずこれが何をしているかというと、対象のinputをRHFの管理下に置いているということ
  • どういうことかというと対象のinputの入力内容をRHFが検知して状態として保持することができるようになる
  • 仕組みとしては、registerの第一引数である name などのラベルに紐づいた onChangeonBlurref を生成し、それをinputに登録しているイメージ
  • registerとは、引数にラベルやバリデーションを受け取り onChangeonBlurref を含むオブジェクトを返すメソッド
  • イメージとしては次のようなオブジェクトを返す
{
  name: "name"
  onChange: () => {},
  onBlur: () => {},
  ref: () => {}
}
  • そのため、registerが返すこのオブジェクトをスプレッド構文を使って展開してinputに登録することで、対象のinputに onChangeonBlurref が登録される仕組み

inputタグのname属性について

  • 基本だが、inputタグのname属性はフォームデータの識別子として使われる
    • 送信するときに各フィールドの名前(キー)として使われる
  • name属性はユニークであるべきか
    • ユニークでなくてもよいが、意味を持たせる必要がある
    • チェックボックスなどで複数のボックスに同じ名前を持たせた場合、同じキー名で値が配列になって渡される
    • RHFではname属性がキーになるので、同じフォーム内ではユニークにするべき
  • RHFでnameを使って複数の入力をグループ化する方法
<form onSubmit={handleSubmit(onSubmit)}>
  <input {...register("user.email")} placeholder="Email" />
  <input {...register("user.username")} placeholder="Username" />
</form>
  • 上記のデータは次のように送られる
<form onSubmit={handleSubmit(onSubmit)}>
  <input {...register("user.email")} placeholder="Email" />
  <input {...register("user.username")} placeholder="Username" />
</form>

RHFがデフォルトでバリデーションを行うタイミング

  • onSubmit
  • onBlur

formState: { errors } について

  • ここの記述に苦手意識があったが、これはネストされた分割構文
  • useForm() の戻り値は次のようなイメージ
{
  register: ....,
  handleSubmit: ...,
  formState: {
    errors: {
      name: ...,
      email: ....,
    }
  }
}
  • この中から、registerやhandleSubmitを抽出するために、 const { register, handleSubmit } = useForm() と記述している
  • const { register, handleSubmit, formState } = useForm() と書いても良いが、その場合は、errorsにアクセスしたいときは、formState.errorsと書かないといけない
  • errorsはよく使うので、const { register, handleSubmit, formState: { errors }} = useForm()としておけば、直接 errors にアクセスできる
  • またこの状態でも formStateにアクセスすることは可能

RHFでhandleSubmitを使う理由

  • event.preventDefault() を行ってくれるので、ユーザーが記述する必要がない
  • フォームの各データを収集してくれる
  • バリデーションを実行し、エラーがあれば formState.errors に格納する
  • (エラーがなければ)収集したデータをオブジェクトとしてonSubmitに渡してくれる
もしhandleSubmitを使わなければ
  • event.prevent.Default()を記述しないといけない
  • onSubmitには FormEvent オブジェクトが渡される
  • 自分で event.target から値を取得しないといけない

registerの第二引数に指定するオプション

  • RHFの苦手なポイントとして、第二引数が複雑というイメージがある

required

  • <input {...register('name', { required: true })} />
  • <input {...register('name', { required: '名前は必須です' })} />

min

  • <input {…register('age', { min: 5 })} >
  • <input {…register('age', { min: {value: 5, message: '年齢は5歳以上である必要があります' } })} >

max

  • <input { ...register( 'age', { max: 5 })} />
  • <input { ...register( 'age', { max: { value: 5, message: '年齢は5歳以下である必要があります' } })} />

minLength

  • <input {…register('name', { minLength: 5 })} />
  • <input {…register('name', { minLength: { value: 5, message: '5文字以上で入力してください' } })} />

maxLength

  • <input {…register('name', { maxLength: 5 })} />
  • <input {…register('name', { maxLength: { value: 5, message: 5文字以下で入力してください } })} />

pattern

  • <input {…register('name', { pattern: { value: /[...]/ , message: '正しい形式で入力してください' } })} />

validate

  • <input {…register('name', { validate: (value)=> value === 'admin' ? 'error' : true })} />
<input {...register('name', { 
  validate: {
    notAdmin: (value) => value !== 'admin' || 'adminは使用できません',
    minLength: (value) => value.length >=5 || '5文字以上で入力してください'
  }
})} />

disabled

  • <input {…register('name', { disabled: true })} />

valueAsNumber

  • <input {…register('name', { valueAsNumber: true })} />

valueAsDate

  • <input {…register('name', { valueAsDate: true })} />

ポイント

  • 大体このようにまとめてみると、第二引数は { パラメータ: boolean } または { パラメータ: 値 }の基本形があり、メッセージを付けたい時には { パラメータ: { value: 値, message: メッセージ } }になるような傾向がある(例外あり)

おわりに

長くなってきたので記事を分けようと思います。今回はReact Hook Formだけを使用したパターンでした。次はzodを組み合わせたバリデーションについてまとめます。