🗓️

React Hook FormのonBlurがDatePicker(MUI)に効かない時の解決方法

2024/10/19に公開

はじめに

useFormmodeonBlur に設定した際に発生した DatePicker コンポーネントの挙動不良について調査した結果をまとめてみました。

問題について

useFormmodeonBlur に設定したところ、DatePicker コンポーネントが正しく動作しないことに気づきました。
調査を進めた結果、DatePicker にはそもそも onBlur プロパティが存在しないことがわかりました。

type Props<T extends FieldValues> = {
  control: ControlForm<T>;
  name: Path<T>;
  rules: Omit<RegisterOptions<FieldValues>, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'>;
};

export const Calendar = <T extends FieldValues>({ name, control, rules }: Props<T>) => {
  return (
    <Controller
      name={name}
      control={control}
      rules={rules}
      render={({ field, fieldState }) => {
        return (
          <LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={ja}>
            <FormControl error={fieldState.error ? true : false} className="ml-sm">
              <DatePicker
                {...field}
                // onBlueは存在しない 
          // onBlur = {field.onBlur}
              />
            </FormControl>
          </LocalizationProvider>
        );
      }}
    />
  );
};

そこでDatePickerの内部で使用しているtextFieldfield.onBlurを渡すことにしました。
DatePickerのプロパティであるslotPropsを使うと、内部で使用されるtextFieldをカスタマイズすることができます。
ちなみにerrorhelperTextslotPropsで指定します。

type Props<T extends FieldValues> = {
  control: ControlForm<T>;
  name: Path<T>;
  rules: Omit<RegisterOptions<FieldValues>, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'>;
};

export const Calendar = <T extends FieldValues>({ name, control, rules }: Props<T>) => {
  return (
    <Controller
      name={name}
      control={control}
      rules={rules}
      render={({ field, fieldState }) => {
        return (
          <LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={ja}>
            <FormControl error={fieldState.error ? true : false} className="ml-sm">
              <DatePicker
                {...field}
                slotProps={{
                  calendarHeader: { format: 'yyyy/MM' },
                  textField: {
                    helperText: fieldState.error.message ?? ' ',
                    error: fieldState.error ? true : false,
                    // ここで渡す。
                    onBlur: field.onBlur,
                  },
                }}
              />
            </FormControl>
          </LocalizationProvider>
        );
      }}
    />
  );
};

これで解決、、?

これで解決かと思いましたが、実際に操作してみると次のような問題が発生しました。

  1. エラーが表示されている状態
  2. 日付を選択する
  3. カレンダーが非表示になり、DatePicker からフォーカスが外れる(この時点でエラーが消えてほしい)
  4. 再度 DatePicker にフォーカスを当てる
  5. ようやくエラーが消える

検討した結果、field.onBluronAccept へ渡すことにしました。
onAccept は、日付の選択を確定した際に呼び出されるイベントで、ここに field.onBlur を設定し、日付選択後にフォーカスが外れたタイミングでエラーが消えるようにしました。

type Props<T extends FieldValues> = {
  control: ControlForm<T>;
  name: Path<T>;
  rules: Omit<RegisterOptions<FieldValues>, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'>;
};

export const Calendar = <T extends FieldValues>({ name, control, rules }: Props<T>) => {
  return (
    <Controller
      name={name}
      control={control}
      rules={rules}
      render={({ field, fieldState }) => {
        return (
          <LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={ja}>
            <FormControl error={fieldState.error ? true : false} className="ml-sm">
              <DatePicker
                {...field}
                slotProps={{
                  calendarHeader: { format: 'yyyy/MM' },
                  textField: {
                    helperText: fieldState.error.message ?? ' ',
                    error: fieldState.error ? true : false,
                    onBlur: field.onBlur,
                  },
                }}
                // ここで渡す。
                onAccept={field.onBlur}
              />
            </FormControl>
          </LocalizationProvider>
        );
      }}
    />
  );
};

まとめ

実は、他の MUI のコンポーネントでも同じように react-hook-formonBlur が効かないことがあるので、こういった調整が必要になるかもしれません。
コンポーネントごとの挙動を理解しながら、より使いやすいフォームを作っていくのが大事ですね!✨

Discussion