react-hook-form + yup tips
概要
本記事では、案件で得た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
)
参考
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