💎
React Hook FormとZodの組み合わせで<select>を使った時にハマったメモ
やりたいこと
セレクトボックスで選択した id を form で送りたいです。
例として、都道府県を選択してprefCode
を number型で送りたいとします。
const prefectures = [
{
prefCode: 1,
prefName: "北海道",
},
{
prefCode: 2,
prefName: "青森県",
},
// 続く...
{
prefCode: 47,
prefName: "沖縄県",
},
];
React Hook Form と Zod
React Hook Form と Zod を組み合わせて簡単なフォームを用意しました。
ドキュメントからコードを引用したのをちょっと変えてます。
(※この記事の最後に関係あるリンク一覧を載せてます。)
index.tsx
import { useForm, SubmitHandler } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
const schema = z.object({
name: z.string(),
prefectureId: z.number(),
});
type User = z.infer<typeof schema>;
export default function Home() {
const {
register,
handleSubmit,
} = useForm<User>({
resolver: zodResolver(schema),
});
const onSubmit: SubmitHandler<User> = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>名前</label>
<input {...register("name")} />
<label>出身県</label>
<select {...register("prefectureId")}>
{prefectures.map((pref) => {
return (
<option key={pref.prefCode} value={pref.prefCode}>
{pref.prefName}
</option>
);
})}
</select>
<input type="submit" />
</form>
);
}
ハマったポイント
selectタグの value は string型しか送れないようです。
<option value={1}>北海道</option>
value に number の1
を入れても"1"
に変換されます。
そのため、id は number型 で欲しいからといって zod で number型を指定してしまうとバリデーションがかかって form送信をすることができません。
解決策
Zod の .tramsform という機能を使えば、解析後にデータを変換できます。
たとえば、以下のように書くと string で受け取った値を number に変更できます。
const schema = z.object({
name: z.string(),
prefectureId: z.string().transform((val) => Number(val)),
});
こうすることで入力はstringで受けるけど、型はnumberになります。
.tramsformを使って型生成してみる
const schema = z.object({
name: z.string(),
prefectureId: z.string().transform((val) => Number(val)),
});
type User = z.infer<typeof schema>;
// ↓Userにホバーするとこうなってる
// type User = {
// name: string;
// prefectureId: number;
// }
出力結果もnumberになる
{name: "kazuho", prefectureId: 1}
どうすればよかったか
エラーハンドリングを書いてたらすぐに気づけたと思います。
全然解決できなくて半日は潰しました。
export default function Home() {
const {
register,
handleSubmit,
// これを追加↓
formState: { errors },
} = useForm<User>({
resolver: zodResolver(schema),
});
const onSubmit: SubmitHandler<User> = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>名前</label>
<input {...register("name")} />
<label>出身県</label>
<select {...register("prefectureId")}>
{prefectures.map((pref) => {
return (
<option key={pref.prefCode} value={pref.prefCode}>
{pref.prefName}
</option>
);
})}
</select>
{/* これを追加↓ */}
{errors.prefectureId?.message && <p>{errors.prefectureId?.message}</p>}
<input type="submit" />
</form>
);
}
参考記事
React Hook Form
Zod
Discussion
を
としても数値で受け取れませんかね?
↑これができれば、スキーマは↓このままでイケる気がしますが。
そういうオプションがあったんですね😲
初めて知りました!ありがとうございます〜!