react-hook-formでメールアドレスやパスワードの再入力時の一致確認バリデーションを実装する方法

久しぶりの記事更新になります。

最近reactを触ることが多くなってきたので、その中でformの実装でお世話になっている「react-hook-form」のお話しになります。

今回はその「react-hook-form」でメールアドレスやパスワードなどの再入力項目のバリデーションの実装方法を紹介します。

目次

まずはこちらのデモを御覧ください。

codesandboxでサンプルを用意しました。
実際に入力してバリデーションの動作確認をしてみてください。

使用した主なライブラリとそのバージョン

今回の記事を書くにあたって検証したバージョンになります。

  • react 18.0.0
  • next.js 12.1.4
  • typescript 4.6.3
  • react-hook-form 7.29.0
  • @hookform/error-message 2.0.0

実装したコードの全体

以下が今回実装したコードの全てになります。
styleなど不要なコードがありますので、重要な部分のみを後述します。

import styles from "../styles/Home.module.scss";

// react-hook-form
import { useForm, SubmitHandler } from "react-hook-form"; // SubmitHandlerは、submitイベントに関する関数の型宣言に使う
import { ErrorMessage } from "@hookform/error-message"; // エラーメッセージコンポーネント

type FormData = {
  email: string;
  email_confirmation: string;
};

const Form = () => {
  // useFormの設定&使用したい機能を呼び出す
  const {
    register,
    handleSubmit,
    formState: { errors },
    getValues,
    trigger
  } = useForm<FormData>({
    mode: "onBlur",
    criteriaMode: "all"
  });

  // submit時の処理
  const onSubmit: SubmitHandler<FormData> = async (data) => {
    console.log(data);
    alert("OK. メールアドレスは一致しています。");
  };

  return (
    <div className={styles["wrapper"]}>
      <form id="form" onSubmit={handleSubmit(onSubmit)}>
        <p>メールアドレスを入力してください。</p>

        {/* メールアドレスを入力 */}
        <div className={styles["form-input"]}>
          <input
            type="email"
            placeholder="sample@a.a"
            {...register("email", {
              required: "メールアドレスを入力してください。",
              onBlur: () => {
                if (getValues("email_confirmation")) {
                  trigger("email_confirmation");
                }
              },
              pattern: {
                value: /^[\w\-._]+@[\w\-._]+\.[A-Za-z]+/,
                message: "入力形式がメールアドレスではありません。"
              }
            })}
          />
        </div>

        {/* メールアドレスを再入力 */}
        <div className={styles["form-input"]}>
          <input
            type="email"
            placeholder="確認のためメールアドレスを再入力"
            {...register("email_confirmation", {
              required: "確認のためメールアドレスを再入力してください。",
              validate: (value) => {
                return (
                  value === getValues("email") || "メールアドレスが一致しません"
                );
              }
            })}
          />
        </div>

        {/* メールアドレスのバリデーションメッセージ */}
        <ErrorMessage
          errors={errors}
          name="email"
          render={({ message }) =>
            message ? (
              <p className={styles["form-validateMessage"]}>{message}</p>
            ) : null
          }
        />

        {/* メールアドレス再入力のバリデーションメッセージ */}
        <ErrorMessage
          errors={errors}
          name="email_confirmation"
          render={({ message }) =>
            message ? (
              <p className={styles["form-validateMessage"]}>{message}</p>
            ) : null
          }
        />

        <button type="submit" className={styles["form-submit-button"]}>
          <span>送信する</span>
        </button>
      </form>
    </div>
  );
};

export default Form;

解説

それでは先程のコードの解説をしたいと思います。

react-hook-form関連をインポート

import { useForm, SubmitHandler } from "react-hook-form";
import { ErrorMessage } from "@hookform/error-message";

これは解説するまでものありませんが、react-hook-formとバリデーションメッセージを表示する@hookform/error-messageをインポートします。

useFormを使い初期設定をする

const {
  register,
  handleSubmit,
  formState: { errors },
  getValues,
  trigger
} = useForm<FormData>({
  mode: "onBlur",
  criteriaMode: "all"
});

このあたりはreact-hook-formを使う上で必須なことなので細かいことは説明しませんが、今回の再入力時の一致確認バリデーションで重要なのものgetValuestriggerです。

バリデーションの実装

それではバリデーションの実装についてお話します。これがこの記事の本編です(笑)

バリデーションの部分を抜き出したのが以下のコードになります。※不要divやclassNameなどは削除しております。

<input
  type="email"
  placeholder="sample@a.a"
  {...register("email", {
    required: "メールアドレスを入力してください。",
    onBlur: () => {
      if (getValues("email_confirmation")) {
        trigger("email_confirmation");
      }
    },
    pattern: {
      value: /^[\w\-._]+@[\w\-._]+\.[A-Za-z]+/,
      message: "入力形式がメールアドレスではありません。"
    }
  })}
/>

<input
  type="email"
  placeholder="確認のためメールアドレスを再入力"
  {...register("email_confirmation", {
    required: "確認のためメールアドレスを再入力してください。",
    validate: (value) => {
      return (
        value === getValues("email") || "メールアドレスが一致しません"
      );
    }
  })}
/>

<ErrorMessage
  errors={errors}
  name="email"
  render={({ message }) =>
    message ? (
      <p>{message}</p>
    ) : null
  }
/>

<ErrorMessage
  errors={errors}
  name="email_confirmation"
  render={({ message }) =>
    message ? (
      <p>{message}</p>
    ) : null
  }
/>

再入力部分

まずは再入力部分のバリデーションから解説します。

<input
  type="email"
  placeholder="確認のためメールアドレスを再入力"
  {...register("email_confirmation", {
    required: "確認のためメールアドレスを再入力してください。",
    validate: (value) => {
      return (
        value === getValues("email") || "メールアドレスが一致しません"
      );
    }
  })}
/>

react-hook-formはregisterにオブジェクトを渡して色々バリデーションを設定できるのですが、そのなかでvalidateを使用します。validateは任意のバリデーションを設定できます。

validate: (value) => {
  return (
    value === getValues("email") || "メールアドレスが一致しません"
  );
}

validateに上記のコードのように設定すると「通常入力項目」と「再入力項目」の比較が行えます。

validateの引数のvalueは再入力項目の値が入っています。

getValuesは第一引数に指定したname属性をもつ要素のvalueを取得します。これにより「通常入力項目」の値を取得しています。

通常入力部分

通常入力部分について解説します。

<input
  type="email"
  placeholder="sample@a.a"
  {...register("email", {
    required: "メールアドレスを入力してください。",
    onBlur: () => {
      if (getValues("email_confirmation")) {
        trigger("email_confirmation");
      }
    },
    pattern: {
      value: /^[\w\-._]+@[\w\-._]+\.[A-Za-z]+/,
      message: "入力形式がメールアドレスではありません。"
    }
  })}
/>

ここで重要なのはonBlurの箇所です。

onBlur: () => {
  if (getValues("email_confirmation")) {
    trigger("email_confirmation");
  }
},

なにをやっているのかというと

入力項目からフォーカスが外れた時にgetValuesで再入力項目の値を取得し、値がある場合にtriggerを使い再入力項目のバリデーションを実行します。

なぜこのような処理が必要かというと、再入力項目のバリデーション設定だけでは2つの項目の入力を完了した後に通常入力項目を変更した際に値が一致しなくなった場合バリデーションが動かないからです。

なのでtriggerで再入力項目のバリデーションも同時に実行してあげる必要があります。

バリデーションメッセージ

これはインポートした@hookform/error-messageを普通に使用しているだけです。

下記のようにnameにinputで設定したnameを渡すだけでバリデーションメッセージが表示されます。

<ErrorMessage
  errors={errors}
  name="email"
  render={({ message }) =>
    message ? (
      <p>{message}</p>
    ) : null
  }
/>

最後に

個人的にreact-hook-formはvalidateで今回のような再入力バリデーションも簡単に実装できるのでかなり気に入ってます。

また、今回はなるべく分かりやすくするため各種inputなどコンポーネント化などはしていないので、使用する場合はそれぞれをコンポーネント化するなり好き使用してください。