📝

【React修行日記】React Hook Form の導入

に公開

学習の目的

  • React Hook Formを使用してフォームを実装できるようになる
  • フォームのバリデーション設定方法を理解する

React Hook Formとは

React Hook Formは、Reactでフォームをシンプルかつ型安全に扱うためのライブラリ。
フォームの入力値・エラー・バリデーション状態などをReactの状態管理に組み込むことなく、軽量に扱えるのが特徴。

主な特徴:

  • 再レンダーが少ない(パフォーマンスが良い)
  • コードがスッキリ(stateやonChangeが不要)
  • バリデーションが直感的に書ける
  • TypeScriptとの相性が良い(型安全なフォームが書ける)

実装ステップ

今回は簡易的なユーザー登録フォームをReact Hook Formを利用して実装してみた...!

①フォームの土台を作る

Tailwind CSSshadcn/uiを使用。

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

export default function UserForm() {
  return (
    <form className="flex flex-col gap-6 mx-auto max-w-[520px]">
      <h2 className="font-bold">ユーザー登録フォーム</h2>

      <div className="flex flex-col gap-3">
        <Label htmlFor="name">名前</Label>
        <Input type="text" id="name" placeholder="例:山田太郎" />
      </div>

      <div className="flex flex-col gap-3">
        <Label htmlFor="email">メールアドレス</Label>
        <Input type="email" id="email" placeholder="例:example@example.com" />
      </div>

      <div className="flex flex-col gap-3">
        <Label htmlFor="age">年齢</Label>
        <Input type="text" id="age" placeholder="例:25" />
      </div>

      <Button>登録する</Button>
    </form>
  );
}

useForm()を導入

まず、React Hook Formをプロジェクトへ追加。

npm install react-hook-form

次に、react-hook-formからuseFormをimportしてフォームの状態を管理できるようにする。
useForm()はReact Hook Formの中核となる関数で、「入力値」「エラー」「バリデーション」「送信処理」などをひとまとめに扱えるようにする。

import { useForm } from "react-hook-form";

type FormData = {
  name: string;
  email: string;
  age: string;
};

export default function UserForm() {
  const { register, handleSubmit } = useForm<FormData>();

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

  return (
    <form
      onSubmit={handleSubmit(onSubmit)}
      className="flex flex-col gap-6 mx-auto max-w-[520px]"
    >

型の定義

フォームで扱うデータの型を定義しておくと、register()などで入力項目を紐付けたときに型補完やエラー防止が効くようになる。

type FormData = {
  name: string;
  email: string;
  age: string;
};

useForm()の呼び出し

useForm()を呼び出すと、フォームの状態管理に必要なさまざまな関数や値をまとめて取得できる。
ここで<FormData>のように型を指定することで、フォームの入力項目に型安全性を持たせられる。

const { register, handleSubmit } = useForm<FormData>();

▽ここでは主に2つの関数を使用

関数名 役割
register 各入力フィールドをReact Hook Formに登録(管理対象にする)
handleSubmit 送信イベント時にフォーム全体を検証&データを渡す

onSubmit関数を定義

handleSubmit()は、フォーム送信時に呼び出され、バリデーションが通っていれば、onSubmit関数に入力データを渡す。
フォームタグのonSubmithandleSubmit(onSubmit)を渡すだけで、入力→送信→バリデーション→データ受け取り、の流れがすべて自動で処理される。

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

<form onSubmit={handleSubmit(onSubmit)}>

register()で入力フィールドをReact Hook Formに接続

Inputコンポーネントにregisterを渡して、React Hook Formにデータを管理させる。

<Input type="text" id="name" placeholder="例:山田太郎" {...register("name")} />
<Input type="email" id="email" placeholder="例:example@example.com" {...register("email")} />
<Input type="text" id="age" placeholder="例:25" {...register("age")} />

register("name")を展開(スプレッド)して渡すことで、この<Input>が「name」というフィールドとしてReact Hook Formに登録される。
これだけで以下のようなことが自動で行われる。

  • 入力値の取得・監視
  • 値の変更による再レンダー制御
  • バリデーションのトリガー管理

ここまでで、フォームの各入力フィールド(name・email・age)がReact Hook Form に登録され、入力内容を自動で管理できる状態になった。
フォーム送信時にはhandleSubmit(onSubmit)によって、入力データがまとめてdataオブジェクトとして取得可能。

④バリデーション(必須チェック&エラーメッセージ表示)

次に、フォームに入力ルール(バリデーション)を追加して、未入力や不正な値を防ぐ処理を追加する。

formState.errorsでエラー情報を取得

useFormからformState: { errors }を取り出すことで、各入力項目に発生したエラー情報へアクセスできる。

const {
  register,
  handleSubmit,
  formState: { errors }, //追加
} = useForm<FormData>();

たとえば、errors.nameには「名前」フィールドのエラー内容が格納され、errors.name?.messageでエラーメッセージを取得できる。

requiredで必須チェックを追加

必須項目としたいInputコンポーネントのregister()の第2引数にバリデーションルールを指定する。
ここではrequiredにエラーメッセージを設定。

{errors.name && (
  <p className="text-left text-sm text-red-500">{errors.name.message}</p>
)}
<Input
  type="text"
  id="name"
  placeholder="例:山田太郎"
  {...register("name", { required: "名前は必須です" })} //追加
/>

⑤バリデーション強化

文字数制限

名前フィールドに文字数制限(例:2〜20文字)を追加。

{...register("name", {
  required: "名前は必須です",
  minLength: { value: 2, message: "2文字以上入力してください" }, //追加
  maxLength: { value: 20, message: "20文字以内で入力してください" }, //追加
})}
  • minLength:入力文字数の最小値
  • maxLength:入力文字数の最大値
  • message:ルールに違反した場合に表示するエラーメッセージ

正規表現チェック

年齢フィールドに半角数字チェックを追加。

{...register("age", {
  pattern: {
    value: /^[0-9]+$/,
    message: "半角数字で入力してください",
  },
})}
  • value:正規表現(RegExp)
  • message:違反時のエラーメッセージ

register関数のオプション
https://react-hook-form.com/docs/useform/register

⑥UI/UX改善

リアルタイムバリデーション

通常、React Hook Formのバリデーションは送信時にのみ実行される。
ユーザーにとっては、入力中に間違いを指摘してもらえるほうがわかりやすい場合があるので、今回は入力中にエラーが即時表示されるように実装。

useForm<FormData>({
  mode: "onChange",
});
  • mode: "onChange"を指定すると、入力値が変更されるたびにバリデーションが実行される
  • エラー表示やisValidの状態も即時更新されるので、ユーザーは間違いをすぐに修正可能

modeの指定方法

モード 説明
onSubmit(デフォルト) フォーム送信時にバリデーションを実行
onChange 入力値が変わるたびバリデーションを実行
onBlur 入力欄からフォーカスが外れたタイミングでバリデーション
all onChangeonBlur 両方でバリデーションを実行

https://react-hook-form.com/docs/useform#mode

登録ボタンの制御

バリデーションがすべて通ったときだけボタンを活性化して、送信可能状態かどうかをわかりやすくすることもできる。

const { register, handleSubmit, formState: { errors, isValid } } = useForm<FormData>({
  mode: "onChange", 
});
:
<Button type="submit" disabled={!isValid}>
  登録する
</Button>
  • mode: "onChange"にすることで、入力があるたびisValidが更新される
  • isValid必須項目とバリデーションルールを全て通っているかを判定する真偽値

formStateの主なプロパティ

プロパティ名 説明
errors 各フィールドのバリデーションエラー情報
isDirty 1度でも入力値が変更されたか の真偽値
dirtyFields 変更されたフィールドの一覧
isSubmitting フォーム送信中か の真偽値(非同期送信時に便利)
isSubmitted 一度でも送信されたか の真偽値
submitCount フォームが送信された回数
isValidating 現在バリデーション中かどうか
touchedFields フォーカスアウト(blur)したフィールドの一覧
isValid 全ての必須チェック・バリデーションを通過しているか の真偽値

▽その他のプロパティ
https://react-hook-form.com/docs/useform/formstate

まとめ

  • React Hook Formを使用することで、簡単にフォームを作成することができる。
  • 必須チェックや文字数制限などのバリデーション設定によって、UI/UXを意識したフォームを作成できる。

参考

https://react-hook-form.com/

Discussion