🗿

react-hook-form + yup tips

2023/02/04に公開

概要

本記事では、案件で得たyupのtipsを記述します。
react-hook-formとyupを併用している為、まずは簡単にreact-hook-formの基本に触れ、
その後、yupのtipsを記述しています。

react-hook-form側の基本

今回、yupのtips紹介がメインの為、詳細なreact-hook-formの解説は省きます。

動作イメージ

react-hook-form + yup 利用の為の基本記述

※一旦、型なしで書きます

import { yupResolver } from '@hookform/resolvers/yup';
import { useForm } from 'react-hook-form';

const errorScheme = yup.object().shape({
  // ここがreact-hook-form側と一致しているとバリデーションが発火
  form_name: yup
    .string()
    .required('エラーメッセージ'),
});

const component = () => {
  const {
    handleSubmit,
    formState: { errors },
  } = useForm({
    mode: 'all',
    criteriaMode: 'all',
    shouldFocusError: false,
    defaultValues: {
      form_name: '初期値', // ここがyup側と一致しているとバリデーションが発火
    },
    resolver: yupResolver(errorScheme),
  });

  // ... 省略
}

useFromに渡すパラメータについて

mode

バリデーションの実行タイミングの設定項目です。
onChange, onBlur, onSubmit, onTouched が指定可能
all → すべてのタイミングでバリデーション実行

criteriaMode

バリデーションエラー発生時の表示モードの設定項目です。
firstError → エラー一つだけ表示
all →すべてのエラーを表示

shouldFocusError

フォーム送信時にバリデーションエラーが発生した場合、
エラーのある最初のフィールドにフォーカスするかどうかの設定項目です。

defaultValues

各フォームに対しての初期値の設定項目です。
※これが設定できてないとreact-hook-form, yup共に動きません。

resolver

yupを動かす為の設定項目です。

型のポイント

useFormに対しての型

defaultValuesに記述した型の通りに記述します。

type SubmitDataArgument = {
  formName: string;
};

useForm<SubmitDataArgument>({
  defaultValues: {
   formName: valueScreen,
  },
})

handleSubmitに対しての型

UseFormHandleSubmitとFieldValuesを利用します

handleSubmit: UseFormHandleSubmit<FieldValues>;

getValues

defaultValuesに記述した型をここでも利用します。

type SubmitDataArgument = {
  formName: string;
};

getValues: UseFormGetValues<SubmitDataArgument>;

errors周り

formState: { errors }, で取得するerrorsに対して型付けを行う場合、
string, numberなどのプリミティブなエラーに対しては、FieldError という型で良いですが、
string[], number[]などの配列型のエラーに関しては、FieldError[]ではなく、 FieldErrors を利用した方が都合が良いです。
FieldError[]としてしまうと、エラー記述をマークアップする際に、messageのプロパティが取得できません(取得しにくい)が、FieldErrorsだとerrors.formName?.message の記述でも型エラーになりません。

yup Tips

ここからが本題です。

文字列 + 必須チェックの場合

yup
  .string()
  .required('必須エラーメッセージ')

数値 + 必須チェックの場合

yup
  .number()
  .required('必須エラーメッセージ')

配列型の必須チェックの場合

arrayには.requiredが利用できなかった為、
.min(1 , ◯◯)を指定することで、1つ以上の選択がない場合にエラーを表示するという意味になり、
requiredと同等の動作をします。

yup
  .array()
  .min(1, '必須エラーメッセージ')

numberだけどnullも許す感じでバリデーションしたいとき

フォームから取得した値をtransform内で一旦string化し、trimした後、
空文字ならnullを返し、そうでなければ数値を返すといった形になります。

yup
.number()
.nullable()
.transform((value, originalValue) =>
  String(originalValue).trim() === '' ? null : value
)

参考
https://tech.motoki-watanabe.net/entry/2021/01/23/205510

date型のチェック

date()を利用します。
ただ、nullが発生したり、実際のフォームはstring型で渡ってきたりする為、
date()の扱い方は以下の様に少し工夫が必要です。

yup
  .date()
  .nullable()
  .typeError('正しい日付を入力しましょう')
  .required('必須エラーメッセージ')

フォーム値による分岐

別のフォーム値によって、バリデーションを発生させるかどうかのif的な切り替えをしたい場合、
when を利用します。

yup.object().shape({
    showEmail: yup.boolean(),
    email: yup
      .string()
      .email()
      .when("showEmail", {
        is: true,
        then: yup.string().required("Must enter email address")
      })
  })

カスタムバリデーション

.test()を利用します。
.test()内で他のフォーム値を参照したい場合、this.parentを利用します。
※ちなみにfunctionを利用しているのは、アロー関数にするとthisの参照が狂う為です。

.test(function(value) {
  // ここにthis.parent.◯◯で記述
  this.parent.formName
})

サンプルコード

formCalendar: yup
  .date()
  .nullable()
  .typeError(ERROR_MESSAGE.REQUIRED_CALENDAR_SELECTED)
  .required(ERROR_MESSAGE.REQUIRED_CALENDAR_SELECTED)
  .test('', function (value) {
    const deadline = this.parent.hiddenFrom
    const selectedDay = dayjs(value)
    const deadlineDay = dayjs().add(+deadline, 'day')
    return dayjs(selectedDay).isAfter(deadlineDay);
  }),
hiddenFrom: yup.string(),

もし、yup内でReactのステートやRecoilのグローバルステートの値を利用したい場合、
ステートの値はyup側に直接記述できない為、工夫が必要です。

仮想的なフォームをreact-hook-form上に生成し、その仮想的なフォームに対して、
ステートの値をsetValue()でセットすれば、yup側でthis.parent.◯◯で値を参照することができます。
こうすることで動的に変わる値を元にしたバリデーションの記述が出来る様になります。

最後に

Reactのフォーム周辺やバリデーション実装の為のライブラリは、数種ありますが、
自分はいくつか試して、今の所、react-hook-form + yup + recoilで落ち着いています。
最近界隈では、zod人気が出てきた為、個人的にはそっちも触ってみたいところです。
Reactでの良きフォーム実装ライフをお送りください。

Discussion