フォームライブラリとバリデーションライブラリのキャスト
Formik + Yup
数値での確認
Yup
test()
でとれるvalue
, ctx.parent
はキャスト後
空の場合はundefined
Formik
formik.values
, onSubmit
のvalue
はキャスト前
空の場合は''
キャストするとエラーが投げられる
React Hook Form + Yup
Yup
キャスト後
空の場合はNaN
React Hook Form
field.value
, handleSubmit
のvalue
はキャスト後
空の場合はフォームがinvalidになる
requiredの場合は問題ないがoptionalのとき数値がうまく扱えない
以下のような対応がある
yup.addMethod(yup.number, "optional", function (this) {
return this.transform((value, originalValue) => {
return originalValue === "" ? undefined : value;
});
});
を追加してoptional()
を利用するようにすると空の場合のキャスト後の値をundefinedにできて丸く収まりそう
日付の場合(Yup + MUI + Day.js)
日付の場合は少し複雑
DateSchema
を利用する場合、transform
でstring
を返すことはできないが、普通はリクエスト時に文字列にする
なのでMixedSchema
でtransform
するかな
そうなるとDate
で値を持っておく必要もないのでDayjs
のまま保持するのが良さそう
// yup.mixed<dayjs.Dayjs>().optionalDate()のように使う
declare module "yup" {
interface MixedSchema {
optionalDate(): this;
}
}
yup.addMethod(yup.mixed, "optionalDate", function (this) {
return this.transform((value) => {
return value === null ? undefined : value.format("YYYY-MM-DD");
});
});
Formik
DatePicker
のonChangeではイベントではなく値だけがくるのでsetFieldValue
する必要
<DatePicker
label="date1"
value={formik.values.date1}
onChange={(date) => formik.setFieldValue("date1", date)}
slotProps={{ field: { clearable: true } }}
/>
React Hook Form + Yupのとき、React Hook Form側でキャストされているのはyupResolver
の設定だった
- キャストしたい時:デフォルト
- キャストしたくない時:
yupResolver(schema, {}, { raw: true })
Yup側でキャストしたくない時は第2引数で{ strict: true }
を指定するけどこれは使わないかな
declare module "yup" {
interface MixedSchema {
optionalDate(): this;
requiredDate(): this;
minDate(minDate: dayjs.Dayjs): this;
maxDate(maxDate: dayjs.Dayjs): this;
}
}
yup.addMethod(yup.mixed, "optionalDate", function (this) {
return this.transform((value) => {
if (!dayjs.isDayjs(value)) return undefined;
return value.format("YYYY-MM-DD");
});
});
yup.addMethod(yup.mixed, "requiredDate", function (this) {
return this.optionalDate().required();
});
yup.addMethod(yup.mixed, "minDate", function (this, minDate: dayjs.Dayjs) {
return this.test("minDate", "minDate", (value) => {
if (value === undefined) return true;
if (typeof value !== "string") return false;
const d = dayjs(value);
return d.isAfter(minDate, "date") || d.isSame(minDate, "date");
});
});
yup.addMethod(yup.mixed, "maxDate", function (this, maxDate: dayjs.Dayjs) {
return this.test("maxDate", "maxDate", (value) => {
if (value === undefined) return true;
if (typeof value !== "string") return false;
const d = dayjs(value);
return d.isBefore(maxDate, "date") || d.isSame(maxDate, "date");
});
});
シリアライズを別でやるならDateSchema
でいい
Zod
Zodはpreprocess
→validation
→transform
の順に行われるよう
export const asNumber = (arg: unknown): number | undefined => {
if (arg === "") {
return undefined;
}
return Number(arg);
};
export const asDate = (arg: unknown): Date | undefined => {
if (dayjs.isDayjs(arg)) {
return arg.toDate();
}
if (typeof arg === "string") {
return new Date(arg);
}
return undefined;
};
export const schema = z.object({
num: z.preprocess(asNumber, z.number().optional()),
date: z
.preprocess(asDate, z.date().optional())
.transform((value) => value?.toJSON()),
});
バリデーションライブラリを使っていてsubmit以外で値を取るシチュエーションがよく分からない
スキーマから生成した型は使わないほうが良さそう