React Hook Formのwatch()は多用しないほうがいい
結論
フォーム全体の値を取得したいとき以外、再レンダリングを最小化してパフォーマンスを保ちたいなら useWatch()
を使う。
➡ 特定の値だけ監視したいなら useWatch()
、全体をざっくり見たいなら watch()
が便利!
🔁 使いどころまとめ
状況・目的 | 適した関数 | 理由 |
---|---|---|
フォーム全体の値を一括で監視したい | watch() |
全体状態を簡単に取得できる |
デバッグ用に値をログ出力したい | watch() |
全体の状態を都度確認しやすい |
小規模なフォームでパフォーマンスが問題にならない | watch() |
パフォーマンス負荷が少ないため |
特定フィールドの変更だけを検知したい | useWatch() |
必要なフィールドだけ購読できる |
大規模フォームやパフォーマンス重視 | useWatch() |
全体再レンダリングを防げる |
一部の UI だけ動的に更新したい | useWatch() |
局所的な再レンダリングが可能 |
watch()とuseWatch()の違い
React Hook Form公式ドキュメントに以下のような記述がありました。
The only difference between useWatch and watch is at the root (useForm) level or the custom hook level update.
「useWatch と watch の唯一の違いは、useForm レベルまたはカスタムフックレベルでの更新にあります。」と書かれています。
つまり、簡単に言えば以下のような違いがあります。
- watch() は「フォーム全体を包括的に監視」する(useFormレベル)。
- useWatch() は「特定のフィールドだけを監視」する(カスタムフックレベル)。
useFormレベルとは、useForm() を定義したコンポーネントとその子コンポーネント全体
以下のコード例でいうと、MyFormコンポーネントとその子コンポーネント全体です。
import { useForm } from 'react-hook-form';
export default function MyForm() {
const { register, watch } = useForm();
return (
<form>
<InputA register={register} />
<InputB register={register} />
</form>
);
}
次にwatch()とuseWatch()の違いをコードで見ていきます。
watch()の場合
"use client";
import { useFormContext, useWatch } from "react-hook-form";
// watch()を使ったコンポーネント
function NameDisplayWatch() {
console.log("🔁 NameDisplayWatch が再レンダリングされました!");
const { watch } = useFormContext();
const name = watch("name");
console.log(name);
return <p>🔁 watch() の値: {name}</p>;
}
export default function Home() {
console.log("🏠 Home コンポーネントが再レンダリングされました!");
const { register } = useFormContext();
return (
<div className="p-10">
<form className="flex flex-col gap-6">
<div className="flex flex-col">
<label htmlFor="name">ユーザー名</label>
<input
id="name"
{...register("name")}
type="text"
className="border rounded-2xl"
/>
</div>
</form>
<div className="mt-10 border-t pt-5 space-y-2">
<NameDisplayWatch />
</div>
</div>
);
}
watch()の場合、「あ」と入力すると...
「Home コンポーネントが再レンダリングされました!」と「NameDisplayWatch が再レンダリングされました!」が表示されます。
つまりNameDisplayWatchコンポーネントだけでなく、Homeコンポーネントもレンダリングされていることになります。
useWatch()の場合
"use client";
import { useFormContext, useWatch } from "react-hook-form";
// useWatch()を使ったコンポーネント
function NameDisplayUseWatch() {
console.log("✅ NameDisplayUseWatch が再レンダリングされました!");
const { control } = useFormContext();
const name = useWatch({ control, name: "name" });
return <p>✅ useWatch() の値: {name}</p>;
}
export default function Home() {
console.log("🏠 Home コンポーネントが再レンダリングされました!");
const { register } = useFormContext();
return (
<div className="p-10">
<form className="flex flex-col gap-6">
<div className="flex flex-col">
<label htmlFor="name">ユーザー名</label>
<input
id="name"
{...register("name")}
type="text"
className="border rounded-2xl"
/>
</div>
</form>
<div className="mt-10 border-t pt-5 space-y-2">
<NameDisplayUseWatch />
</div>
</div>
);
}
先ほどと同じように「あ」と入力すると...
今度は「NameDisplayUseWatch が再レンダリングされました!」のみが表示されており、Home コンポーネントは再レンダリングされていません。
つまり、今回はNameDisplayUseWatchコンポーネントのみがレンダリングされています。
レンダリング構造まとめ(イメージ)
watch()とuseWatch()のレンダリングの違いをまとめると以下のようになります。
watch() の場合:
Home 🔁(再レンダリング)
└─ NameDisplayWatch 🔁(再レンダリング)
useWatch() の場合:
Home(再レンダリングなし)
└─ NameDisplayUseWatch 🔁(再レンダリング)
僕は正直今までwatch()しか使ったことがありませんでした...
これからはuseWatch()を積極に使いつつ、フォーム全体の値を使う時のみwatch()を使うようにしたいと思います。
参考リンク
Discussion