8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

react-hook-form と zod による、基本的なバリデーション付きフォーム

Last updated at Posted at 2024-03-25

はじめに

本記事では、React 用のフォームライブラリ「react-hook-form」と「zod」の使い方を解説します。react-hook-form と zod を使えば、最小限のコードでバリデーション付きフォームを実装できます。

本記事を読むことで、以下のことが学べます。

  • react-hook-form の基本的な使い方
  • zod を使った基本的なスキーマ定義とバリデーション
    • refine を使った高度なバリデーション
  • TypeScriptでの記述時の型のポイント

※ コードのみの説明にするため、Water.css による最低限のデザインになっています。

react-hook-form のみでシンプルなフォームを作る

まずは、名前とメールアドレスだけの簡単なフォームを作ってみましょう。react-hook-form では、useFormフックを使ってフォームの状態を管理します。

Screenshot 2024-03-25 at 9.08.00.png

import { useForm } from "react-hook-form";

export default function MyForm() {
  const { register, handleSubmit } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input type="text" placeholder="名前" {...register("name")} />
      <input type="email" placeholder="メールアドレス" {...register("email")} />
      <button type="submit">送信</button>
    </form>
  );
}

ここでのポイントは以下の 3 つです。

  • useFormフックを使ってフォームの状態を管理
  • register関数でフォームのフールドを登録
  • handleSubmit関数で送信時の処理を定義

useFormはフォーム全体の状態を管理するためのフックで、バリデーションやエラーハンドリングなどの機能を提供します。

registerは、フォーム内の各フィールドをuseFormに登録するための関数です。これにより、react-hook-form がフィールドの状態を追跡できるようになります。

handleSubmitは、フォーム送信時に呼び出される関数を定義するためのユーティリティ関数です。ここでは、フォームのデータを受け取って処理するonSubmit関数を渡しています。

以上が、react-hook-form を使った基本的なフォームの実装例です。

デフォルト値とリセット機能の追加

次に、メールアドレスのデフォルト値を設定し、リセットボタンを追加してみましょう。

Screenshot 2024-03-25 at 9.13.07.png

import { useForm } from "react-hook-form";

export function MyForm(props) {
  const { defaultValues } = props;
  const { register, handleSubmit, reset } = useForm({
    defaultValues,
  });

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input type="text" placeholder="名前" {...register("name")} />
      <input type="email" placeholder="メールアドレス" {...register("email")} />
      <button type="submit">送信</button>
      <button type="button" onClick={() => reset()}>
        リセット
      </button>
    </form>
  );
}

// MyFormのpropsにdefaultValuesを渡します
export default function App() {
  return (
    <MyForm
      defaultValues={{
        email: "test@example.com",
      }}
    />
  );
}

useFormフックには、defaultValuesオプションを指定することで、各フィールドのデフォルト値を設定できます。ここでは、emailフィールドに初期値を与えています。

また、reset関数を使うと、フォームを初期状態にリセットすることができます。ここではリセットボタンを押すと、defaultValuesで指定した値に戻ります。

zod によるバリデーションの追加

基本的にフォームには、ユーザ入力のバリデーションが必須です。react-hook-form では、resolverオプションを使って、柔軟なバリデーションを行えます。

ここでは、zod というスキーマ定義ライブラリを使って、フィールドのバリデーションを追加します。

Screenshot 2024-03-25 at 9.25.29.png

Screenshot 2024-03-25 at 9.25.37.png

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

// zodを使って、フィールドのスキーマを定義します
const schema = z.object({
  name: z.string().min(1, "名前は必須です"),
  email: z.string().email("正しいメールアドレスを入力してください"),
  age: z.number().min(18, "18歳以上である必要があります"),
});

export default function MyForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    // zodResolver関数を使って、バリデーション用のリゾルバを作成し、
    // そのまま作成したリゾルバを渡します
    resolver: zodResolver(schema),
  });

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input type="text" placeholder="名前" {...register("name")} />
      {errors.name && <span style={errorStyle}>{errors.name.message}</span>}

      <input type="email" placeholder="メールアドレス" {...register("email")} />
      {errors.email && <span style={errorStyle}>{errors.email.message}</span>}

      <input
        type="number"
        placeholder="年齢"
        {...register("age", { valueAsNumber: true })}
      />
      {errors.age && <span style={errorStyle}>{errors.age.message}</span>}

      <button type="submit">送信</button>
    </form>
  );
}

const errorStyle = {
  color: "red",
};

zod を使ったバリデーションは以下のようなステップで行います。

  1. zod を使って、フィールドのスキーマを定義
  2. zodResolver関数を使って、バリデーション用のリゾルバを作成
    • リゾルバは、フォームの入力値をチェックするためのバリデーションロジックを定義する関数です。zodResolver関数は、zod で定義したスキーマを受け取り、それに対応するリゾルバ(バリデーション関数)を作成します
  3. useFormフックのresolverオプションに、作成したリゾルバを渡す

zod のスキーマ定義は非常に直感的で、必須チェックや型チェックなどを簡単に行えます。

また、バリデーションエラーが発生した場合は、formState.errorsオブジェクトからエラーメッセージを取得して表示できます。

refine を使用した複雑なバリデーション

refine を使用したもう少し複雑なバリデーションの例を見てみましょう。

Screenshot 2024-03-25 at 9.23.40.png

Screenshot 2024-03-25 at 9.24.12.png

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

const schema = z
  .object({
    password: z
      .string()
      .min(8, "パスワードは8文字以上で入力してください")
      .regex(/^[a-zA-Z0-9]+$/, "パスワードは英数字のみで入力してください"),
    confirmPassword: z.string(),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: "パスワードが一致しません",
    path: ["confirmPassword"],
  });

export default function MyForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        type="password"
        placeholder="パスワード"
        {...register("password")}
      />
      {errors.password && (
        <span style={errorStyle}>{errors.password.message}</span>
      )}

      <input
        type="password"
        placeholder="パスワード(確認用)"
        {...register("confirmPassword")}
      />
      {errors.confirmPassword && (
        <span style={errorStyle}>{errors.confirmPassword.message}</span>
      )}

      <button type="submit">送信</button>
    </form>
  );
}

const errorStyle = {
  color: "red",
};

ここでは、passwordconfirmPasswordという 2 つのパスワード入力欄を設け、それらが一致することを検証しています。

zod のrefineメソッドを使うと、複数のフィールドを跨いだバリデーションを行えます。この例では、passwordconfirmPasswordが等しいかどうかをチェックし、一致しない場合はエラーを設定しています。

TypeScript のサンプルコード

上述の zod のバリデーション付きフォームを TypeScript で書き直してみましょう。

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

// フィールドのスキーマを zod で定義
const schema = z.object({
  name: z.string().min(1, "名前は必須です"),
  email: z.string().email("正しいメールアドレス形式で入力してください"),
  age: z.coerce.number().min(18, "18歳以上である必要があります"),
});

// フォームの入力値の型を上述のスキーマから作成
type FormValues = z.infer<typeof schema>;

export default function MyForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data: FormValues) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input type="text" placeholder="名前" {...register("name")} />
      {errors.name && <span>{errors.name.message}</span>}

      <input type="email" placeholder="メールアドレス" {...register("email")} />
      {errors.email && <span>{errors.email.message}</span>}

      <input type="number" placeholder="年齢" {...register("age")} />
      {errors.age && <span>{errors.age.message}</span>}

      <button type="submit">送信</button>
    </form>
  );
}

まず、z.infer<typeof schema> を使って、zod のスキーマから TypeScript の型を推論します。
この FormValues 型を useForm に渡すことで、フォームの入力値の型を指定できます。

そして coerce メソッドを使うことで、文字列型の入力値を数値型に変換します。

まとめ

react-hook-form は、シンプルで使いやすく、かつ柔軟性の高いフォームライブラリです。zod もシンプルで使いやすく、かつ柔軟性の高いバリデーションライブラリです。本記事では、一部の機能を紹介しましたが、react-hook-form と zod にはまだまだ多くの機能があります。

以下に公式ドキュメントを参照しておきます。

執筆時点の各種バージョン

  • "react": "18.2.0",
  • "react-dom": "18.2.0",
  • "react-hook-form": "7.51.0",
  • "zod": "3.22.4"
  • "@hookform/resolvers": "3.3.4"
  • "typescript": "5.2.2"
8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?