react-hook-formのメリット
1. 高パフォーマンス(最小限のリレンダリング)
- react-hook-form は 「非制御コンポーネント (uncontrolled) ベース」 の設計で、
入力値を React の state に都度詰めず、ref 経由で管理します。 - その結果、再レンダリング回数が少なく、大きなフォームでも軽いのが特徴です。
- 公式サイトでも「Minimizes the number of re-renders, minimizes validate computation」と明記されており、
パフォーマンス重視のフォーム向けに設計されていることが分かります。
2. コードがシンプルで、ボイラープレートが少ない
-
useFormとregister,handleSubmitなど、API がシンプルで覚えやすい。 - 各 input に
value/onChangeを自前で書かず、{...register("email")}の一行で
「値の管理+バリデーション+submit 時の値取得」まで面倒を見てくれます。 - そのため、フォームの行数がかなり減り、可読性も維持しやすいです。
フォームが 10 項目を超えてくると、
「全部useStateで持つ or 大きなオブジェクトで管理する」よりも、
react-hook-form のほうが圧倒的に見通しが良くなります。
3. バリデーション設計がしやすく、拡張もしやすい
-
resolverという仕組みで、Yup・Zod・Joi などのスキーマバリデーションライブラリと簡単に連携できる。 -
バリデーションルールを「スキーマ」としてまとめられるので、
- どんなルールがあるかが一望できる
- 再利用もしやすい
- バックエンドと共通化した設計も取りやすい
-
単純な必須チェックから、複数項目をまたいだ条件付きバリデーションまで、柔軟に書けます。
4. 大規模・複雑なフォームにも対応しやすい
-
FormProvider+useFormContextを使うことで、
ネストしたコンポーネント間でフォーム状態を共有しやすい設計になっています。 - フィールド配列(行の追加・削除があるような明細フォームなど)も
useFieldArrayで扱えるため、動的フォームとの相性も良いです。 - これらを活かすと、「行追加できる明細」「条件によって表示項目が変わるフォーム」など
実務でよくあるパターンを無理なく実装できます。
5. UI ライブラリとの相性が良い
- MUI / Chakra UI / Mantine といった UI ライブラリの input コンポーネントとも組み合わせやすく、
Controllerを使えば「React Hook Form + カスタムコンポーネント」を自然に接続できます。 - すでに UI コンポーネントライブラリを使っているプロジェクトでも、
デザインを崩さずにフォームだけ react-hook-form に移行しやすいです。
react-hook-form の基本
1. useForm でフォームを初期化する
まずはコンポーネントの先頭で useForm を呼び出します。
import { useForm } from "react-hook-form";
type FormValues = {
email: string;
password: string;
};
export const LoginForm = () => {
const {
register, // input とフォームロジックを紐づける
handleSubmit, // 送信時に呼ぶ
formState: { errors }, // バリデーション結果など
} = useForm<FormValues>();
// 後述
};
-
useForm<FormValues>()の<FormValues>は フォームの型(TypeScript 用) -
ここで返ってくるオブジェクトから、フォーム操作に使う関数を取り出します
registerhandleSubmit-
formState.errorsなど
2. register を input に渡す
register を各フィールドに「展開」するのが、react-hook-form の基本スタイルです。
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>メールアドレス</label>
<input
type="email"
{...register("email", {
required: "メールアドレスは必須です",
})}
/>
{errors.email && <p style={{ color: "red" }}>{errors.email.message}</p>}
</div>
<div>
<label>パスワード</label>
<input
type="password"
{...register("password", {
required: "パスワードは必須です",
minLength: {
value: 8,
message: "8文字以上で入力してください",
},
})}
/>
{errors.password && (
<p style={{ color: "red" }}>{errors.password.message}</p>
)}
</div>
<button type="submit">ログイン</button>
</form>
);
何をしているか?
-
register("email", {...})
→ 「この input はemailという名前のフィールドです」と react-hook-form に登録 -
第 2 引数のオブジェクトで、バリデーションルール も一緒に指定できる
required: "必須メッセージ"-
minLength,maxLength,pattern,validateなども書ける
-
エラーは
formState.errorsに入るので、errors.email?.messageで表示
ポイント
-
valueやonChangeを自分で書かなくていい -
...register()を展開するだけで- 値の登録
- バリデーション設定
- 送信時の値取得
を全部やってくれる
3. handleSubmit で送信処理とバリデーションをまとめる
フォームの <form> タグには、onSubmit={handleSubmit(onSubmit)} を指定します。
const onSubmit = (data: FormValues) => {
// バリデーション OK のときだけ呼ばれる
console.log(data);
// data.email / data.password が入っている
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* 入力フィールドたち */}
</form>
);
handleSubmit(onSubmit) の役割:
- 全フィールドのバリデーションを実行
- エラーがあれば
errorsにセット →onSubmitは呼ばれない - 問題なければ、フォームの値を引数
dataとしてonSubmitに渡す
なので、onSubmit の中では「もうバリデーション済み」という前提で
API 呼び出しなどの処理を書いて OK です。
react-hook-formで出来ないこと
1. ビジネスロジックやドメインルールまでは面倒を見てくれない
- 「18歳未満ならこの項目は必須」
- 「このプランを選んだときだけ上限が変わる」
- 「社内の申請フローに合わせた複雑なチェック」
こういう業務・ドメイン寄りのルールは、
react-hook-form が自動で判断してくれるわけではなく、結局は自分でロジックを書く必要があります。
2. バックエンドとのスキーマ同期はしてくれない
- DB のスキーマ
- API のリクエスト/レスポンス定義
- OpenAPI/Swagger など
これらと 自動で連携してバリデーションルールを生成する機能はありません。
- Zod や Yup と組み合わせれば「フロント内の」スキーマ共通化はできる
- でも「バックエンドからスキーマを引っ張ってきて自動生成」まではしない
ので、バックエンドと完璧に一元管理したいなら別の仕組み(codegen など)が必要です。
3. マルチステップフォーム / 画面またぎの状態管理はやってくれない
react-hook-form は「1 つのフォーム」の中身を扱うのが主な役割です。
- ステップ1 → ステップ2 → 確認画面 → 完了画面
- 画面 A で入力して、画面 B で続き、画面 C で確認
みたいな「ページをまたぐウィザード形式」は、
- react-hook-form で各ページのフォームを持つ
- その上で、親コンポーネントやグローバル state で全体を保持する
といったラッピング設計が必要で、
「マルチステップフォームをワンライナーで作る」ような機能はありません。
4. 複雑すぎる動的フォームの設計までは助けてくれない
useFieldArray で
- 行の追加/削除がある明細フォーム
- ネストしたリスト
などは扱えますが、
- ネストが深くなりすぎる
- 条件分岐が多すぎる
- フィールド名が動的に決まる
といった設計そのものの複雑さは、
react-hook-form を入れても勝手に解決されません。
例:
- 「1ページに 50 項目 × 10 行」
- 「ロールによって表示項目が変わる」
→ ライブラリが悪いというより、「フォーム設計そのものがカオス」なケースも多い
他ライブラリと組み合わせたときの可能性
1. バリデーションライブラリとの連携で「スキーマ駆動フォーム」に
@hookform/resolvers を使うと、Yup / Zod / Joi / Vest など、好きなバリデーションライブラリをそのまま組み込めます。
- バリデーションルールをコード上の スキーマ(schema) として定義
- そのスキーマを React Hook Form の
resolverに渡す - スキーマを変更すれば、フォーム側のチェックも自動的に追従
Zod のような型付きスキーマと組み合わせれば、
-
「スキーマ → TypeScript の型 → フォーム」 という流れで
「型」と「バリデーション」と「フォーム構造」が揃った、
スキーマ駆動フォーム に発展させることもできます。
2. UI コンポーネントライブラリと組み合わせた「きれいで壊れにくいフォーム」
MUI / Chakra UI / Mantine などの UI ライブラリは、見た目が整った入力コンポーネントを提供してくれますが、多くは「controlled component」になっています。
React Hook Form の Controller / useController を使うと、こうしたコンポーネントともスムーズに接続できます。
-
Controllerがvalue/onChange/onBlurなどを橋渡ししてくれる - UI ライブラリ側は「いつも通り使うだけ」で、裏側の値管理は React Hook Form に任せられる
- サードパーティ用の公式/コミュニティ製バインディング集(Material UI, Chakra, AntD など)も用意されている
結果として、
- デザインは UI ライブラリに任せる
- 入力値の管理・バリデーションは React Hook Form に任せる
という きれいな責務分離 ができ、「見た目も中身も強いフォーム」が作りやすくなります。
3. データフェッチ・状態管理と組み合わせた「実務的な画面フロー」
React Hook Form はあくまで「フォームの中」の責務なので、
送信後の処理は TanStack Query や Zustand / Redux などと組み合わせて設計します。
例:
-
handleSubmitの中で TanStack Query のmutateを呼んで API に POST する - 送信中は
isSubmittingでボタンを disabled にして二重送信防止 - 成功時は React Router で別ページへ遷移 / モーダルを閉じる
- 失敗時はトーストライブラリ(react-hot-toast など)でエラーメッセージ表示
こうすることで、「フォーム → API → 画面遷移」まで一続きの UX を作れます。
4. API スキーマやコードジェネレータと組み合わせた「フロント〜バックの型安全」
OpenAPI / tRPC / GraphQL などと、Zod や TypeScript の型生成を組み合わせると、
- バックエンドの API スキーマから型や Zod スキーマを自動生成
- 生成されたスキーマを
zodResolverで React Hook Form に渡す - バックエンドとフロントのバリデーションルール&型が自動で同期
という構成も取れます。
結果的に、
- 仕様変更があっても「スキーマ → 型 → フォーム」が一気に更新される
- 「バックエンドでは NG なのにフロントでは通ってしまう」といったズレを減らせる
という 型安全・仕様同期されたフォーム実装 に近づけます。