✍️

React Hook FormでAPIの初期値をフォームに反映させる正しい方法【resetの使い方】

に公開

APIで取得したデータをdefaultValuesにセットする方法

公式ドキュメントから以下抜粋します。

defaultValues are cached. To reset them, use the reset API.

上記のように「defaultValuesはキャッシュされます。リセットするには、resetAPIを使用してください。」と明言されていました。
つまり以下のようにdefaultValuesをセットしなおしたいときは、reset()を使います。

・APIから取得したデータをフォームにセットするとき(初期化)
・ユーザーがフォームを「リセットボタン」で初期状態に戻したいとき
・フォームの入力モードを切り替えたいとき
・フォーム送信後に値をクリアしたいとき
・ステップフォームで次のステップに進むときに、値をリセットしたい
・ログインユーザーが変わったときにフォームの内容をリセットする

const { register, reset, ... } = useForm();

useEffect(() => {
  const fetchData = async () => {
    const res = await fetch('/api-endpoint');
    const data = await res.json();
    reset(data); // フォームに初期値をセット
  };
  fetchData();
}, []);

下記のように直接変えようとしても変更できません。

const methods = useForm({
  defaultValues: { name: 'Taro' }
});

// 後から値を変えても watch() は反応しません
methods.defaultValues.name = 'Jiro'; // ❌ UIには反映されない

下記のようにします。

// 正しくは reset() を使う
reset({ name: 'Jiro' }); // ✅ このあと watch("name") は 'Jiro' になる

デフォルト値を非同期に設定する方法として、ReactHookFormで以下のような書き方があったのですが、reset()でセットするのとどちらが推奨なのでしょうか?(わかる方コメントください。)
なお defaultValues: async () => fetch(...) の書き方は公式ドキュメントに記載がありますが、React 18 以降の StrictMode の影響や初期レンダリングの制御が難しいため、個人的には reset() で後からセットする方法の方が実務的には安定していると感じています。

useForm({
  defaultValues: async () => fetch('/api-endpoint');
})

defaultValues vs watch の違い

初期値を取得する方法として、下記のように主に二つあると思っています。

const form = useForm({
  defaultValues: {
    name: 'Taro',
  }
});

console.log(form.defaultValues.name); // 常に 'Taro'
console.log(watch("name")); //ユーザーが何か入力すれば即座に反映される。

上記の例で言うと、
・defaultValues.nameで取得する。
・watch("name")で取得する。

defaultValues.nameのほうは、フォームが初期化された時点での値を指し、その後の入力や変更には影響されません(静的な値)。
一方で、watch("name")の場合は、フォーム内の"name"フィールドのリアルタイムな現在の値を取得し、ユーザーが何か入力すれば即座に反映されます。また、入力変更やsetValueに反応します。
※ watch() は再レンダリングをトリガーするので、頻繁な使用や大規模フォームでは useWatch や useMemo でのラップを検討すると良いです。

Discussion