こんにちは、フロントエンドチームの 谷(@high_g_engineer)です。
今週のフロントエンド定例の内容を記載します。
フロントエンド定例について、以前の記事(ランサーズのフロントエンドチームが取り組んでいること)でお伝えしたのですが、毎週金曜日に開催しており、実際の業務で取り組んでいることや気になった技術情報等をシェアしあう会になっています。
以下、今週の内容です。
概要
本記事では、案件で得た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での良きフォーム実装ライフをお送りください。
ランサーズでは、フロントエンドエンジニアを随時募集しておりますので、まずはお話でもという方はご連絡くださいませ!
フロントエンドエンジニア(本社)の採用情報 | ランサーズ株式
前回の定例内容はこちらから確認可能ですのでご興味いただければ下記のリンクから閲覧いただければと思います。