title | emoji | type | topics | published | |||||
---|---|---|---|---|---|---|---|---|---|
React Hook Form + zodでselectフォームを作る際のはまりどころ |
🦍 |
tech |
|
true |
const schema = z.object({
cost: z.number(),
});
type Schema = z.infer<typeof schema>;
const COSTS = [1000, 2000, 3000, 5000, 10000] as const;
const Form = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<Schema>({
resolver: zodResolver(schema),
defaultValues: {
cost: 0,
},
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<select {...register('cost')}>
{COSTS.map((cost) => (
<option key={cost} value={cost}>
{cost}
</option>
))}
</select>
<button type="submit">送信</button>
</form>
);
};
export default Form;
このコードだとデフォルト値の場合はエラーが起きませんが、select で選択した値を送信するとエラーが起きます。 watch を使ってみると、選択した値は数値型ではなく文字列型になっていることがわかります。
console.log(watch('cost'));
select の value は文字列型になるため。
<option value={1}>React</option>
value に number を入れても文字列型になってしまいます。厄介ですね。
https://react-hook-form.com/api/useform/register#options
RHF のregister
のオプションに存在します。
上記のプログラムを修正してみます。
<select {...register('cost', { valueAsNumber: true })}>
number 型になったことにより、バリテーションを通過するようになりました。
string として受け入れてから number に変換することで解決できます。 zod の schema を修正してみます。
const schema = z.object({
cost: z.string().transform((val) => Number(val)),
});
RHF と zod を使って select フォームを作る際には、number へのキャストが必要なことがわかりました。 RHF にはハマりどころが多いので、理解しながらうまく付き合っていきたいですね!