🦁

React Hook Form 7.48.2へのアップデート:defaultValueの挙動変更への対応

2023/11/28に公開

背景

皆さん react-hook-form(以降 RHF)を使用していますか?
最近、バージョン 7.22.2 から 7.48.2 へとアップデートしたところ、resetFields のdefaultValueオプションの動作が以前とは異なることに気づきました。

この問題に直面したのは、プロップスの変更を検知し、RHF で管理されている特定のフィールド値を更新したいと考えたときでした。以前のバージョンでは、useEffect を使用して resetField 関数で値の更新を行っていましたが、バージョンアップ後、この方法では期待通りに動作しなくなりました。

システムの概要を簡単に説明すると、1つのページに2つの独立したフォームを設置しており、それぞれが別々に設定および更新可能なUIを構築していました。また、簡単に仕様を説明すると以下の通りです:

  1. フォーム A では、開催日程(scheduledAt)を設定できる
  2. フォーム B では、承認期限(approvalDeadlineAt)を設定できる
  3. 承認期限が設定されている場合、開催日程は承認期限より後の日付でなければならない。つまり、フォーム A とフォーム B は「相関関係」にある

この3番目のバリデーションルールをzodを用いて定義しようと考えました。そこで、フォーム A でのフィールド値を RHF で管理する際に、フォーム B の承認期限も一緒に管理するようにしました。以下は、その zod スキーマの概要です:

import { z } from 'zod';

const schema = z.object({
  scheduledAt: z.date.nullable(),
  approvalDeadlineAt: z.date().nullable()
}).superRefine(({ scheduledAt, approvalDeadlineAt }, ctx)) => {
  // ...(バリデーションロジック)...
})

// 以降のコード
// ...

さらに、フォーム B で承認期限が変更されたとき、フォーム A の RHF で管理している承認期限の値も更新するようにしました。

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

// スキーマの定義
const schema = z.object({
  // ...スキーマの詳細
});

type FormValue = z.infer<typeof schema>;

// フォーム A のカスタムフック
const useFormA = (defaultValues: FormValue) => {
  const { approvalDeadlineAt } = defaultValues
  // useFormの使用
  const rhfMethods = useForm<FormValues>({
    defaultValues,
    resolver: zodResolver(schema)
  });
  const { resetField } = rhfMethods;

  // 承認期限の変更を検知して、値を更新する
  // この処理が期待する動きをしなくなった😱
  useEffect(() => {
    resetField('approvalDeadlineAt', { defaultValue: approvalDeadlineAt }); 
  }, [approvalDeadlineAt, resetField]);

  return {
    rhfMethods
  };
}

// フォーム A コンポーネント
const FormA = (args: { defaultValues: FormValues }) => {
  const { register } = useHogeForm(args.defaultValues);
  
  return (
    <form>
      <input type='date' {...register('scheduleAt')} />
    </form>
  );
};

結論

React Hook Form(RHF)のregister機能に関して、公式ドキュメントには以下のようなヒントがありました:

You can also register inputs with useEffect and treat them as virtual inputs.
公式より抜粋

このヒントに基づき、useEffect 内で register 関数を使用することにより、バージョンアップに伴う問題を解決しました。

// フォーム A のカスタムフック
const useFormA = (defaultValues: FormValue) => {
  const { approvalDeadlineAt } = defaultValues
  // useFormの使用
  const rhfMethods = useForm<FormValues>({
    defaultValues,
    resolver: zodResolver(schema)
  });
- const { resetField } = rhfMethods;
+ const { resetField, register } = rhfMethods;

+ useEffect(() => {
+   register('approvalDeadlineAt')
+ }, [register])
 
  useEffect(() => {
    resetField('approvalDeadlineAt', { defaultValue: approvalDeadlineAt }); 
  }, [approvalDeadlineAt, resetField]);

  return {
    rhfMethods
  };
}

まとめ

仮想の入力フィールドや条件分岐で描画される入力フィールドについては、useEffect 内で register 関数を使用することが有効です。

Discussion