React Hook Form の getValues で UI が更新されなかった話
この記事は「Medley (メドレー) Advent Calendar 2025」の6日目の記事となります。
はじめに
こんにちは、メドレーの村上です。
今回は React Hook Form で複数のフォーム値に依存した制御を実装した際に遭遇した問題とその解決方法について共有します。
使用しているライブラリとバージョンはこちらです。
{
"react": "^19.1.0",
"react-hook-form": "7.54.2"
}
発生した問題
以下のように、複数のフォーム値に依存して「ゲスト予約を許可できるか」を計算するロジックがありました。ゲスト予約は、クレジットカード登録と保険証登録の両方が不要な場合のみ許可されます。
// カスタムフック内で計算ロジックを定義
const calculateAllowGuest = useCallback(() => {
const { creditCardRequired, insuranceCardRequired } = getValues("service");
return creditCardRequired === false && insuranceCardRequired === false;
}, [getValues]);
// 計算結果を表示するコンポーネント
export const AllowGuestStatus: FC<Props> = ({ calculateAllowGuest }) => {
const allowGuest = calculateAllowGuest();
return <Text>{allowGuest ? "許可する" : "許可しない"}</Text>;
};
期待する動作は、クレジットカード登録と保険証登録の値が変更されたら、allowGuest の表示も即座に更新されることです。
しかし、フォーム値を変更しても allowGuest の表示が更新されませんでした。
試しに、関連する setValue の呼び出しに以下のオプションを付けると、更新されるようになりました。
setValue("service.guestReservationEnabled", false, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true,
});
一見解決したように見えましたが、なぜこのオプションで動くようになったのか理解できていなかったため、原因を調べました。
原因調査
1. getValues を確認
まず getValues について確認しました。
React Hook Form の公式ドキュメントによると、getValues は呼び出し時点でのフォーム値を取得するヘルパーであり、値の変更を監視する機能はありません。
An optimized helper for reading form values. The difference between watch and getValues is that getValues will not trigger re-renders or subscribe to input changes.
つまり、calculateAllowGuest を呼び出しても、その後フォーム値が変わったことを検知して再計算されることはありません。UI を更新するには、何らかの方法でコンポーネントを再レンダリングさせる必要がありました。
2. setValue のオプションを確認
次に、setValue のオプションについて確認しました。
setValue("service.guestReservationEnabled", false, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true,
});
それぞれのオプションについて調べると、役割は以下の通りでした。
| オプション | 役割 |
|---|---|
shouldDirty |
フィールドの dirty 状態(初期値から変更されたか)を更新する |
shouldTouch |
フィールドの touched 状態(ユーザーが操作したか)を更新する |
shouldValidate |
バリデーションを実行し、errors 状態を更新する |
React Hook Form の公式ドキュメントには、再レンダリングが発生する条件として以下の記載があります。
Only the following conditions will trigger a re-render:
- When an error is triggered or corrected by a value update.
- When setValue cause state update, such as dirty and touched.
これらのオプションで formState が更新されると、その状態を subscribe しているコンポーネントが再レンダリングされることが分かりました。
3. 各オプションの有無で動作を比較
各オプションを個別に外して動作を確認したところ、shouldValidate: true が再レンダリングの原因であることが分かりました。
- shouldValidate: true の場合:UI が正しく更新された
- shouldValidate: false の場合:UI が更新されなかった
4. なぜ shouldValidate で再レンダリングが発生したのか
親コンポーネントで以下のように errors を subscribe していました。
// 親コンポーネント
const { errors } = useFormState({ control });
shouldValidate: true によってバリデーションが実行されると formState.errors が更新され、それを subscribe している親コンポーネントが再レンダリングされます。その結果、子コンポーネントである AllowGuestStatus も再レンダリングされ、getValues が最新の値を取得できていた、という流れでした。
調査結果
これらから、getValues はスナップショットを返すだけで値の変更を監視しないため、依存する値が変わっても再計算されないことが分かりました。setValue のオプションで動いていたのは、バリデーション実行による formState.errors の更新で親コンポーネントが再レンダリングされ、その副作用として子コンポーネントも再レンダリングされていたからでした。
これは本来意図した動作ではなく、errors の subscribe に依存した実装でした。
解決方法
useWatch を使用してリアクティブに値を監視する
React Hook Form には useWatch というフックがあり、特定のフォーム値の変更を監視し、変更時に再レンダリングを発生させることができます。
修正後のコンポーネントはこちらです。
export const AllowGuestStatus: FC<Props> = ({ control }) => {
const creditCardRequired = useWatch({ control, name: "service.creditCardRequired" });
const insuranceCardRequired = useWatch({ control, name: "service.insuranceCardRequired" });
const allowGuest = !creditCardRequired && !insuranceCardRequired;
return <Text>{allowGuest ? "許可する" : "許可しない"}</Text>;
};
また、setValue のオプションも不要になったため削除しました。
// Before
setValue("service.guestReservationEnabled", false, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true,
});
// After
setValue("service.guestReservationEnabled", false);
useWatch を使用することで、setValue のオプションがなくても UI が正しく更新されるようになりました。
なぜ解決できたのか
useWatch は内部で control._subscribe を使用してフォーム値の変更を監視しています。指定した name のフォーム値が変更されると、コンポーネントの再レンダリングを発生させます。これにより、必要な値の変更だけを検知して再計算が行われるようになっていました。
まとめ
React Hook Form の setValue のオプションで UI が更新されていたのは、親コンポーネントの再レンダリングによる副作用でした。フォーム値の変更を検知して UI を更新するには、useWatch を使用するのが正しいアプローチでした。
getValues と useWatch の違いを理解し、用途に応じて適切に使い分けることが重要だと改めて感じました!
終わりに
メドレーでは様々な職種で人材を募集しているので、興味がある方はぜひお声かけください。
Medley Advent Calendar 2025、明日は @shigerisa さんです!
Discussion