個人的にReact Hook Formで気をつけていること
はじめに
React Hook Formの特徴や個人的に気をつけていることをまとめました💫
React Hook Formの特徴
- レンダリング回数を減らすことができる
- バンドルサイズの軽減が可能
レンダリングについて
通常のReactでは、stateで値を管理し、stateを更新(setState)するたびに再レンダリングが発生します。
一方、React Hook Formでは、値の管理にstateではなくrefを使用するため、再レンダリングを抑制できます。
この仕組みにより、フォーム操作時に不要な再レンダリングを減らすことが可能です。
詳細は公式ドキュメントにも記載されています。

-
stateで値を管理する方法はControlled Componentsと呼ばれます。 - 一方、
refで値を管理する方法はUncontrolled Componentsと呼ばれます。
| Type | Controlled Components | Uncontrolled Components |
|---|---|---|
| 管理方法 |
state (React)での管理 |
ref (DOM)での管理 |
| レンダリング | 値が変化するたびに再レンダリングが発生する | 値が変化しても再レンダリングが発生しない |
個人的にReact Hook Formで気をつけているポイント
1. 値を取得する際のレンダリングコスト
簡単なフォーム(ログインフォームなど)であれば問題ないのですが、フォームの値を取得して何か処理をしたい場合には、少し複雑になります。
React Hook Formで値を取得する方法には、getValues、watch、useWatchの3種類があります。
| 種類 | getValues | useWatch | watch |
|---|---|---|---|
| 再レンダリング | 発生しない | コンポーネント単位で発生 | フォーム全体で発生 |
| 使用タイミング | 値を単に取得する場合 | 値の取得+UI/UX更新 | 値の取得+UI/UX更新 |
-
getValuesは再レンダリングを発生させないため、UI/UXの更新(たとえばエラーメッセージの表示など)には使用できません。 -
watchはフォーム全体に再レンダリングを発生させます。 - 一方、
useWatchはコンポーネント単位で再レンダリングを発生させるため、watchよりも影響範囲が狭いです。
しかし、useFormContextでコンポーネント化せずに、直接フォーム配下にuseWatchを記述してしまうと、watchと同様にフォーム全体を再レンダリングしてしまいます。
export default function App() {
const { register, handleSubmit, control } = useForm({ mode: "onBlur" });
const onSubmit = (data) => console.log(data);
const useWatchValue = useWatch({ control, name: "useWatch" });
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* これだと意味がなく、フォーム全体が再レンダリングされてしまう */}
<input {...register("useWatch")} />
{useWatchValue}
<button type="submit">Submit</button>
</form>
);
}
このように、React Hook Formの仕様を理解していないと、強みである「再レンダリングの抑制」の効果が薄れてしまいます。
2. Controllerの使用
外部コンポーネント(MUIなど)を使用する際、Controllerでコンポーネントをラップするのが一般的です。
ただ、ControllerでラップされたコンポーネントはControlled Components同様に再レンダリングが発生します。
本来、Uncontrolled Componentsの利点としてレンダリングの抑制を活かせるはずですが、Controllerを使うことでその効果が損なわれてしまいます。
では、なぜControllerでラップする必要があるのでしょうか。
これは、多くの外部コンポーネントがReactのrefを直接受け取る仕組みに対応していないためです。そのため、Controllerを利用して、フォームの制御を行う必要が出てきます。
React公式ドキュメントでは、refに関して次のように説明しています。
独自のコンポーネント、例えば
<MyInput />に ref を置こうとすると、デフォルトでは null が返されます。以下はそれを示す例です。ボタンをクリックしても入力フィールドにフォーカスが当たらないことに注意してください。
これは、デフォルトでは React は、コンポーネントが他のコンポーネントの DOM ノードにアクセスできないようにしているためです。自分自身の子でさえもです! これは意図的なものです。ただでさえ ref は控えめに使うべき避難ハッチ (escape hatch) です。別のコンポーネントの DOM ノードまで手動で操作できてしまうと、コードがさらに壊れやすくなってしまいます。
代わりに、内部の DOM ノードを意図的に公開したいコンポーネントは、そのことを明示的に許可する必要があります。コンポーネントは、自身が受け取った ref を子のいずれかに「転送 (forward)」するよう指定できます。MyInput が forwardRef API を使ってこれをどのように行うのか見てみましょう。
このように、独自のコンポーネントにrefを割り当てるにはforwardRefを使用する必要があると明記されています。
ただ、外部コンポーネントはforwardRefに対応していないことが多いので、Controllerでラップする必要があるということだと思います。
(MUIは割とforwardRefに対応してるコンポーネントが多いからControllerを使用しなくてもいけそう?)
まとめ
フォームが複雑になったり外部コンポーネントを使用したりする場合は、再レンダリングの影響を受けやすくなります。
利用する際には仕様をしっかり理解し、意図した通りにパフォーマンスを引き出せるようにしましょう💫
参考記事
Discussion