🧚‍♀️

Zodのcoerce機能で解決:HTMLのinput type="number"が文字列になる問題

に公開

🤔 こんな問題に悩んでいませんか?

<input type="number" id="count" />

👆 このフォーム要素、見た目は数値入力用なのに、JavaScript側では、

const value = document.getElementById('count').value;
console.log(typeof value); // "string" 😱

文字列として扱われてしまいます!

📝 よくある解決パターン

こんな解決パターンを見かけることがあります。

// フォーム用とサーバー用、2つの型定義を作成
interface FormData {
  count: string;  // フォームからの入力は文字列
}

interface ProcessedData {
  count: number;  // 処理時は数値
}

このパターンだと、コードが冗長になり、型の不一致によるバグも発生しやすい問題があります。

✅ Zodのcoerce機能で解決!

Zodのcoerce.number()を使えば、この問題を解決できます。

import { z } from "zod";

// たった1つの型定義でOK! 🎉
const formSchema = z.object({
  count: z.coerce.number().int().min(1)
});

type FormData = z.infer<typeof formSchema>;

🚀 React Hook Formでの実装例

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

function CounterForm() {
  const { register, handleSubmit } = useForm<FormData>({
    resolver: zodResolver(formSchema)
  });

  const onSubmit = (data: FormData) => {
    console.log(typeof data.count); // "number" 🎯
    // 数値として直接計算できる!
    const doubled = data.count * 2;
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input type="number" {...register("count")} />
      <button type="submit">送信</button>
    </form>
  );
}

⚡ Next.jsのServer Actionsでも

'use server'

// Server Actionsでも同じスキーマが使える! 🔄
export async function saveCount(formData: FormData) {
  const data = formSchema.parse({
    count: formData.get('count')
  });
  
  // data.countは既に数値型 🧮
  // データベースに保存する処理...
}

💡 coerce.number()のポイント

  • 空文字列 → 0 に変換
  • 文字列 "123" → 数値 123 に変換
  • バリデーションも同時に設定可能(.int().min()など)

🌟 メリット

  • 型定義の一元化: フォーム用とサーバー用で別々の型が不要に
  • コードの簡素化: 変換処理を書く手間が省ける
  • 型安全性の向上: フォーム入力からサーバー処理まで一貫した型で扱える

🎯 まとめ

Zodのcoerce.number()を使えば、HTMLの<input type="number">から取得した値が文字列型になる問題をサクッと解決できます。コードはシンプルに、型安全性は向上し、開発効率もアップします!

Discussion