🔢

react-hook-formとzodでnumber型を扱う時の個人的ベストプラクティス

2024/06/06に公開

結局フォームでは文字列で扱うのが安定

汎用的なメソッドを用意しておく

const NumberFromString = z
  .string()
  .regex(/^[+-]?\d*\.?\d+$/, { message: "数値を入力してください" })
  .transform(Number);
const NumberFromZenkakuString = z
  .string()
  .transform(zenkkaku2hankaku)
  .pipe(NumberFromString);
const validateTime = z
  .number()
  .int()
  .min(0, { message: "0-23の数値を入力してください" })
  .max(23, { message: "0-23の数値を入力してください" });
const validateMinute = z
  .number()
  .int()
  .min(0, { message: "0-59の数値を入力してください" })
  .max(59, { message: "0-59の数値を入力してください" });

全角入力を許すなら以下も追加

export const zenkkaku2hankaku = (str: string) => {
  return str.replace(/[-]/g, (s) => {
    return String.fromCharCode(s.charCodeAt(0) - 0xfee0);
  });
};

usage

const scheduleSchema = z.object({
  startTimeHour: NumberFromZenkakuString.pipe(validateTime),
  startTimeMinute: NumberFromZenkakuString.pipe(validateMinute)
})

このスキーマを使ってreact-hook-formでフォームを作成すると、フォームの値の型はstringになる

APIなどに渡す前にキャストしてやること

const { getValue } = useForm<z.infer<typeof scheduleSchema>>()
const formInput = getValue()
const input = {
	startTimeHour = parseInt(zenkaku2hankaku(formInput.startTimeHour))
}
	

キャスト面倒だけど仕方ない

type=numberやtype=dateはデフォルト値の設定が面倒だし、デザインが気に入らないケースもあるから使い勝手が悪いので当分使わない

参考リンク

https://scrapbox.io/mrsekut-p/zod.pipe()

Discussion