🦍

React Hook Form + zodでselectフォームを作る際のはまりどころ

2023/02/24に公開2

select を使うと型が合わない

一見問題なさそうなコード

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 を入れても文字列型になってしまいます。厄介ですね。

解決策1

valueAsNumber を使う

https://react-hook-form.com/api/useform/register#options
RHF のregisterのオプションに存在します。
上記のプログラムを修正してみます。

 <select {...register('cost', { valueAsNumber: true })}>

number 型になったことにより、バリテーションを通過するようになりました。

解決策2

zod の transform を使う

https://zod.dev/?id=transform

string として受け入れてから number に変換することで解決できます。
zod の schema を修正してみます。

const schema = z.object({
  cost: z.string().transform((val) => Number(val)),
});

まとめ

RHF と zod を使って select フォームを作る際には、number へのキャストが必要なことがわかりました。
RHF にはハマりどころが多いので、理解しながらうまく付き合っていきたいですね!

GitHubで編集を提案

Discussion

nap5nap5

バリデーションをつけてハンドリングする場合は、customを使ってやるとうまくワークアラウンドできるかもです。

const schema = z.object({
// cost: z.number().max(100, 'Something went wrong...'),
  cost: z.custom<Number>().refine(
    (value) => {
      return value <= 3000
    },
    (value) => {
      return {
        message: `${3000}以下にしてください`,
      }
    }
  ),
})

デモコードです。
https://codesandbox.io/p/sandbox/serene-bassi-rjjpzk?file=%2FREADME.md

/costページがデモページになります。

簡単ですが、以上です。

katayama8000katayama8000

ご教授いただきありがとうございます。
custom知りませんでしたが、確かに便利ですね。
今後の開発に役立ちそうです。