15
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React Hook Form 入門 Part 1 — 仕組みを理解してから使う

15
Posted at

React Hook Form 入門 — 仕組みを理解してから使う Part 1

読了時間の目安: 約10分 · React Hook Form v7 対応

「とりあえず動いた」ではなく、なぜ動くのかを理解しながら React Hook Form(以下 RHF)を使えるようになることを目標にします。この記事ではライブラリの導入理由・基本 API・よく誤解されるポイントを丁寧に解説します。


React でフォームを作るときの悩み

まず RHF を使う前に、素の React でフォームを管理するとどんな問題が起きるか整理しましょう。

問題 内容
state が分散する フィールドが増えるほど useState が増え、コンポーネントが肥大化する
再レンダリングが多い 1文字入力するたびに全フィールドが再レンダリングされる
バリデーションが複雑 エラー管理・タイミング制御を自前で実装すると一貫性を保つのが難しい
再利用しにくい フォームのロジックが UI と密結合しており、別のフォームに流用できない

これらは小さなフォームでは気にならないかもしれません。しかし項目が 10 個以上になったり、複雑なバリデーションが入ったりすると、一気に管理コストが跳ね上がります。


React Hook Form はどう解決するか

RHF の核心は uncontrolled components を優先する という設計思想です。

controlled(通常の React) uncontrolled(RHF のデフォルト)
値の管理 React の state で管理 DOM 自身が値を持つ
再レンダリング 入力のたびに発生 入力中は発生しない
値の取得 onChange で随時同期 送信時に ref 経由でまとめて取得

ポイント: RHF は「入力値を React state に同期しない」ことでパフォーマンスを担保しています。これが "高速" と言われる理由の本質です。

加えて、バリデーションのエラー管理・送信ハンドリング・デフォルト値の設定など、フォームに必要なロジックが useForm ひとつに集約されます。


インストールと最初の useForm

# npm
npm install react-hook-form

# yarn
yarn add react-hook-form

バージョン 7 以降、TypeScript のサポートが内蔵されているため別途 @types は不要です。

useForm が返す主なプロパティは次の 3 つです。

const {
  register,       // フィールドを RHF に登録する
  handleSubmit,   // 送信時のラッパー関数
  formState,      // errors・isSubmitting などの状態を持つオブジェクト
} = useForm();

基本的なフォームの実装例

名前・メール・パスワードの 3 フィールドを持つ登録フォームを作ってみます。

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

type FormValues = {
  name: string;
  email: string;
  password: string;
};

export default function RegisterForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>();

  const onSubmit = (data: FormValues) => {
    console.log(data); // バリデーション通過後のみ呼ばれる
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>

      {/* 名前 */}
      <input
        placeholder="名前"
        {...register("name", {
          required: "名前は必須です",
          minLength: { value: 2, message: "2文字以上入力してください" },
        })}
      />
      {errors.name && <p>{errors.name.message}</p>}

      {/* メール */}
      <input
        type="email"
        placeholder="メールアドレス"
        {...register("email", {
          required: "メールアドレスは必須です",
          pattern: {
            value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
            message: "正しいメール形式で入力してください",
          },
        })}
      />
      {errors.email && <p>{errors.email.message}</p>}

      {/* パスワード */}
      <input
        type="password"
        placeholder="パスワード (8文字以上)"
        {...register("password", {
          required: "パスワードは必須です",
          minLength: { value: 8, message: "8文字以上入力してください" },
        })}
      />
      {errors.password && <p>{errors.password.message}</p>}

      <button type="submit">登録</button>
    </form>
  );
}

よく誤解されるポイント 3 選

1. register は何をしているのか

{...register("name")} を spread すると、実際には次のプロパティが input に渡されます。

{
  name: "name",
  ref: refCallback,   // DOM ノードを RHF の内部ストアに登録する
  onChange: handler,  // バリデーションのトリガーのみ(state 更新ではない)
  onBlur: handler,
}

要点: ref によって DOM ノードへの参照を取得しているのが核心です。値は React state ではなく DOM の input.value に留まります。これが uncontrolled の実態です。

2. なぜ RHF は速いのか(もう少し具体的に)

通常の controlled form で 10 フィールドあると、次のような流れになります。

1文字入力 → setState → コンポーネント全体が再レンダリング → 10フィールド全部が再評価

RHF では入力中は React の state が変わらないため、そのコストがゼロです。エラー表示が必要になった瞬間だけ最小限の更新が走ります。フィールドが多いほど、この差は顕著に現れます。

3. formState はいつ再レンダリングを起こすか

formState は「購読した値が変わったときだけ」再レンダリングをトリガーします。

// errors だけ使う場合 → errors が変化したときだけ再レンダリング
const { formState: { errors } } = useForm();

// isSubmitting も使う場合 → errors または isSubmitting が変わると再レンダリング
const { formState: { errors, isSubmitting } } = useForm();

注意: formState を丸ごと変数に入れてしまうと、全プロパティを購読したとみなされます。必ず分割代入で必要なものだけ取り出しましょう。

// NG: 全プロパティを購読してしまう
const { formState } = useForm();

// OK: 必要なものだけ取り出す
const { formState: { errors, isSubmitting } } = useForm();

まとめ — どんなフォームに向いているか

RHF が特に力を発揮するケース:

  • フィールド数が多い(5 個以上)登録・設定フォーム
  • リアルタイムバリデーションが必要なフォーム
  • Zod・Yup などのスキーマバリデーションと組み合わせたい場合
  • フォームロジックをカスタムフックとして切り出して再利用したい場合
  • パフォーマンスがシビアなモバイル向け UI

無理に使わなくてもよいケース:

  • フィールドが 1〜2 個の単純な検索ボックスや問い合わせフォーム
  • フォームの値を親コンポーネントとリアルタイムで同期しなければならない設計

15
5
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
15
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?