カテゴリー: FrontEnd

React Hook Form (v7) とMUI (v5) だけでバリデーションを実装

はじめに

React Hook Formは、Yupなどのvalidation系パッケージと組み合わせて使うことができますが、複雑なバリデーションが不用な場合は、React Hook Form単体でもバリデーションを実装することができます。今回は、MUIのhelperTextにReact Hook Formで検知したバリデーションエラーを表示させる方法を紹介します。

バリデーションエラーをMUIのhelperTextに表示する

バリデーション実装の全体感

まずはシンプルに、20文字以内の入力必須のテキストフィールドと、選択必須のフィールドを作ってみます。MUIのコンポーネントを使用するため、Controllerを使用します。

import { Button, MenuItem, Stack, TextField } from "@mui/material";
import { VFC } from "react";
import { Controller, SubmitHandler, useForm } from "react-hook-form";

interface IceCreamType {
  label: string;
  value: string;
}

interface FormInput {
  userName: string;
  iceCreamType: IceCreamType;
}

const ices = [
  { value: "chocolate", label: "チョコレート" },
  { value: "strawberry", label: "ストロベリー" },
  { value: "vanilla", label: "バニラ" }
];

export const ExampleForm: VFC = () => {
  // バリデーションエラーを表示するため、 formState: { errors }を使用する
  const {
    control,
    handleSubmit,
    formState: { errors }
  } = useForm<FormInput>();

  const onSubmit: SubmitHandler<FormInput> = (data) => {
    console.log(data);
  };

  return (
    <>
      <form onSubmit={handleSubmit(onSubmit)}>
        <Stack spacing={2} padding={2}>
          <Controller
            name="userName"
            control={control}
            defaultValue=""
            // バリデーションルールを定義
            rules={{
              required: "入力してください",
              maxLength: { value: 20, message: "20文字以内にしてください" }
            }}
            render={({ field }) => (
              <TextField
                {...field}
                label="お名前"
                // バリデーションエラーがあればhelperTextに表示する
                error={!!errors.userName}
                helperText={errors.userName && errors.userName.message}
              />
            )}
          />
          <Controller
            name="iceCreamType"
            control={control}
            defaultValue=""
            // バリデーションルールを定義
            rules={{ required: "選択してください" }}
            render={({ field }) => (
              <TextField
                label="アイスクリーム"
                {...field}
                select
                // バリデーションエラーがあればhelperTextに表示する
                error={!!errors.iceCreamType}
                helperText={errors.iceCreamType && errors.iceCreamType.message}
              >
                {ices.map((ice) => (
                  <MenuItem key={ice.value} value={ice.value}>
                    {ice.label}
                  </MenuItem>
                ))}
              </TextField>
            )}
          />
          <Button type="submit" variant="contained">
            送信
          </Button>
        </Stack>
      </form>
    </>
  );
};

上記のコード簡単に説明します。まずは、バリデーションエラーを表示するために必要なhookです。

  // バリデーションエラーを表示するため、 formState: { errors }を使用する
  const {
    control,
    handleSubmit,
    formState: { errors }
  } = useForm<FormInput>();

errorsは FieldErrors<TFieldValues>型となっているので、次のように、useFormで指定した型のプロパティ名でエラーを取得することができます。

  console.log(errors.userName?.message);
  console.log(errors.iceCreamType?.message);

ここでいう userNameiceCreamTypeは、エラーが無い場合はundfinedとなるので、この仕組みを使ってMUIのhelperTextに表示させます。

      <form onSubmit={handleSubmit(onSubmit)}>
        <Stack spacing={2} padding={2}>
          <Controller
            name="userName"
            control={control}
            defaultValue=""
            // バリデーションルールを定義
            rules={{
              required: "入力してください",
              maxLength: { value: 20, message: "20文字以内にしてください" }
            }}
            render={({ field }) => (
              <TextField
                {...field}
                label="お名前"
                // バリデーションエラーがあればhelperTextに表示する
                error={!!errors.userName}
                helperText={errors.userName && errors.userName.message}
              />
            )}
          />
      </form>

上記のコードで、Controllerコンポーネントのrulesに、バリデーションルールを登録していきます。用意されているバリデーションとしては、次のものがあります。

  • required
  • min / max
  • minLength/ maxLength
  • pattern

そして、TextFieldコンポーネントのerrorとhelperTextプロパティをセットすれば、バリデーションエラーが表示されるようになります。

他のフィールドに関連するバリデーション

他のフィールドに関連するバリデーションを作成するには、 validateにチェック用関数を渡すことで、実装することができます。ここでは、メールアドレスの確認フォームを追加してみます。

          <Controller
            name="email"
            control={control}
            defaultValue=""
            // バリデーションルールを定義
            rules={{
              required: "入力してください"
            }}
            render={({ field }) => (
              <TextField
                {...field}
                label="メールアドレス"
                // バリデーションエラーがあればhelperTextに表示する
                error={!!errors.email}
                helperText={errors.email && errors.email.message}
              />
            )}
          />
          <Controller
            name="emailConfirm"
            control={control}
            defaultValue=""
            // バリデーションルールを定義
            rules={{
              required: "入力してください",
              validate: (value) => {
                if (value !== getValues("email")) {
                  return "メールアドレスが一致しません";
                }
              }
            }}
            render={({ field }) => (
              <TextField
                {...field}
                label="メールアドレスの確認"
                // バリデーションエラーがあればhelperTextに表示する
                error={!!errors.emailConfirm}
                helperText={errors.emailConfirm && errors.emailConfirm.message}
              />
            )}
          />

2つめのControllerでvalidateに関数を渡しています。この時の第一引数には、入力された値が入ります。関数の中で他のパラメータを使用するには、 useFormフックの getValues()を使用します。
バリデーションエラーがあれば、表示させたいメッセージをreturnすることで、 errorsにメッセージが入ります。

CodeSandbox

今回のコードをまとめたCodeSandboxを公開しています。
https://codesandbox.io/s/react-hook-form-f0mwh?file=/src/ExampleForm.tsx

 

さいごに

React Hook Formはフォーム実装のコードを軽減することができるので、今後も機能をキャッチアップして使っていきたいと思います!

おすすめ書籍

カイザー

シェア
執筆者:
カイザー

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

2週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

3週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前