shinke1987.net
雑多な備忘録等のはず。
他のカテゴリ・タブ
目次
PR

React Hook Form + Yup の動作確認

2024-05-20 2024-05-20
カテゴリ: React

環境

React:v18.3.1

React Hook Form:v7.51.4

@hookform/resolver:v3.4.0

yup:v1.4.0

yup-locale-ja:v1.0.0

参考

公式サイト

フォームのバリデート等を行うためのライブラリ。

Yupというスキーマビルダーと組み合わせるとバリデーションルール記述箇所とJSXを分割できる。

それぞれのコードを実際に動かして確認すると良い。

RHFのみの場合

インストール

% npm install react-hook-form

RHF2.tsxの内容

import { ReactNode } from 'react';
import { useForm } from 'react-hook-form';

function RHF2(): ReactNode {
  type FormData = {
    name: string,
    mail: string,
    prefectures: string,
    municipality: string,
  };

  const {
    register,
    handleSubmit,
    reset,
    getValues,
    formState: {
      errors,
      isDirty,
      dirtyFields,
      touchedFields,
      isValid,
      isSubmitting,
      submitCount,
    },
  } = useForm<FormData>({
    defaultValues: {
      name: '',
      mail: '',
      prefectures: '',
      municipality: '',
    },
  });

  // 送信ボタンのイベントハンドラ。
  const onSubmit = (data) => {
    console.log('onSubmit data = ', data);

    return new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, 2000);
    });
  };

  // バリデーションエラー時に実行される関数。
  const onError = (data) => {
    console.log('onError data = ', data);
  };

  // リセットボタンのイベントハンドラ。
  const onReset = (data) => {
    console.log('onReset data = ', data);
    reset();
  };

  const validateMessage = {
    required: '入力してください',
    maxLength: '5文字以下で入力してください',
  };

  // スプレッド構文が禁止されている場合は次のように書くと良い。
  const {
    onChange, onBlur, ref, name,
  } = register('municipality', {
    required: validateMessage.required,
    maxLength: {
      value: 5,
      message: validateMessage.required,
    },
  });

  return (
    <>
      <div>
        <div>
          {`送信ボタン押下回数(submitCount):${submitCount.toString()}`}
        </div>

        <br />
        <div>
          {`変更(isDirty):${isDirty.toString()}`}
        </div>

        <br />
        <div>
          {`エラー(全体)(isValid):${isValid.toString()}`}
        </div>
      </div>

      <br />
      <form onSubmit={handleSubmit(onSubmit, onError)} noValidate>
        <div>
          <div>
            <label htmlFor="name">
              名前
            </label>
            <input
              type="text"
              id="name"
              {
                ...register('name', {
                  required: validateMessage.required,
                  maxLength: {
                    value: 5,
                    message: validateMessage.maxLength,
                  },
                })
              }
            />
          </div>
          <div>
            <div>
              {`変更(dirtyFields):${dirtyFields.name?.toString()}`}
            </div>
            <div>
              {`操作(isTouched):${touchedFields.name?.toString()}`}
            </div>
            <div>
              {`エラー情報(error):${errors.name?.message}`}
            </div>
          </div>
        </div>

        <br />
        <div>
          <div>
            <label htmlFor="mail">
              メールアドレス
            </label>
            <input
              type="email"
              id="mail"
              {
                ...register('mail', {
                  required: validateMessage.required,
                  maxLength: {
                    value: 5,
                    message: validateMessage.maxLength,
                  },
                  pattern: {
                    value: /[0-9]/i,
                    message: 'メールアドレスの形式が不正です',
                  },
                })
              }
            />
          </div>
          <div>
            <div>
              {`変更(dirtyFields):${dirtyFields.mail?.toString()}`}
            </div>
            <div>
              {`操作(isTouched):${touchedFields.mail?.toString()}`}
            </div>
            <div>
              {`エラー情報(error):${errors.mail?.message}`}
            </div>
          </div>
        </div>

        <br />
        <div>
          <div>
            <label htmlFor="prefectures">
              都道府県
            </label>
            <select
              id="prefectures"
              {
                ...register('prefectures', {
                  required: validateMessage.required,
                })
              }
            >
              <option value="1">
                北海道
              </option>
              <option value="2">
                東京
              </option>
              <option value="3">
                沖縄
              </option>
            </select>
          </div>
          <div>
            <div>
              {`変更(dirtyFields):${dirtyFields.prefectures?.toString()}`}
            </div>
            <div>
              {`操作(isTouched):${touchedFields.prefectures?.toString()}`}
            </div>
            <div>
              エラー(invalid):
            </div>
            <div>
              {`エラー情報(error):${errors.prefectures?.message}`}
            </div>
          </div>
        </div>

        <br />
        <div>
          <div>
            <label htmlFor="municipality">
              市町村
            </label>
            <input
              type="text"
              id="municipality"
              disabled={getValues('prefectures') === ''}
              onChange={onChange}
              onBlur={onBlur}
              ref={ref}
              name={name}
            />
          </div>
          <div>
            <div>
              {`変更(dirtyFields):${dirtyFields.municipality?.toString()}`}
            </div>
            <div>
              {`操作(isTouched):${touchedFields.municipality?.toString()}`}
            </div>
            <div>
              {`エラー情報(error):${errors.municipality?.message}`}
            </div>
          </div>
        </div>

        <br />
        <div>
          <button
            type="submit"
            disabled={(submitCount === 0 ? false : !isDirty || !isValid) || isSubmitting}
          >
            送信
          </button>
          {isSubmitting && <div>...送信中...</div>}
        </div>

        <br />
        <div>
          <button
            type="button"
            onClick={onReset}
          >
            リセット
          </button>
        </div>
      </form>
    </>
  );
}

export default RHF2;

RHF + Yupの場合

参考

インストール

スキーマビルダー・検証ライブラリのYupとyup-locale-jaを下記コマンドでインストール。

% npm install @hookform/resolvers yup yup-locale-ja

RhfWithYup.tsxの内容

import { useForm } from 'react-hook-form';
import * as yup from 'yup';
import { suggestive } from 'yup-locale-ja';
import { ReactNode } from 'react';
import { yupResolver } from '@hookform/resolvers/yup';

// yup-locale-ja有効化。
yup.setLocale(suggestive);

// バリデーションルール追加。
yup.addMethod(yup.string, 'originalRule', function () {
  return this.test(
    'originalRule',
    ({ label }) => `${label}にzが含まれています`,
    (value) => !value?.includes('z'),
  );
});

// バリデーションルール設定。
const schema = yup.object({
  // register関数の引数のnameと一致させる必要がある。
  name: yup.string()
    .label('名前')
  // NFKCでUnicode正規化される。送信時に確認可能。
  // 例:アイウエオ → アイウエオ
    .transform((value) => value.normalize('NFKC'))
    .required()
    .max(5),
  mail: yup.string()
    .label('メールアドレス')
    .required()
    .email()
  // 上で作成したバリデーションルール。
    .originalRule(),
  prefectures: yup.string()
    .label('都道府県')
    .required(),
  municipality: yup.string()
    .label('市町村')
    .required()
    .max(7),
});

function RhfWithYup(): ReactNode {
  type FormData = {
    name: string,
    mail: string,
    prefectures: string,
    municipality: string,
  };

  const {
    register,
    handleSubmit,
    reset,
    getValues,
    formState: {
      errors,
      isDirty,
      dirtyFields,
      touchedFields,
      isValid,
      isSubmitting,
      submitCount,
    },
  } = useForm<FormData>({
    defaultValues: {
      name: '',
      mail: '',
      prefectures: '',
      municipality: '',
    },
    // Yupに検証を委ねる。
    resolver: yupResolver(schema),
  });

  // 送信ボタンのイベントハンドラ。
  const onSubmit = (data) => {
    console.log('onSubmit data = ', data);

    return new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, 2000);
    });
  };

  // バリデーションエラー時に実行される関数。
  const onError = (data) => {
    console.log('onError data = ', data);
  };

  // リセットボタンのイベントハンドラ。
  const onReset = (data) => {
    console.log('onReset data = ', data);
    reset();
  };

  // スプレッド構文が禁止されている場合は次のように書くと良い。
  const {
    onChange, onBlur, ref, name,
  } = register('municipality');

  return (
    <>
      <div>
        <div>
          {`送信ボタン押下回数(submitCount):${submitCount.toString()}`}
        </div>

        <br />
        <div>
          {`変更(isDirty):${isDirty.toString()}`}
        </div>

        <br />
        <div>
          {`エラー(全体)(isValid):${isValid.toString()}`}
        </div>
      </div>

      <br />
      <form onSubmit={handleSubmit(onSubmit, onError)} noValidate>
        <div>
          <div>
            <label htmlFor="name">
              名前
            </label>
            <input
              type="text"
              id="name"
              {...register('name')}
            />
          </div>
          <div>
            <div>
              {`変更(dirtyFields):${dirtyFields.name?.toString()}`}
            </div>
            <div>
              {`操作(isTouched):${touchedFields.name?.toString()}`}
            </div>
            <div>
              {`エラー情報(error):${errors.name?.message}`}
            </div>
          </div>
        </div>

        <br />
        <div>
          <div>
            <label htmlFor="mail">
              メールアドレス
            </label>
            <input
              type="email"
              id="mail"
              {...register('mail')}
            />
          </div>
          <div>
            <div>
              {`変更(dirtyFields):${dirtyFields.mail?.toString()}`}
            </div>
            <div>
              {`操作(isTouched):${touchedFields.mail?.toString()}`}
            </div>
            <div>
              {`エラー情報(error):${errors.mail?.message}`}
            </div>
          </div>
        </div>

        <br />
        <div>
          <div>
            <label htmlFor="prefectures">
              都道府県
            </label>
            <select
              id="prefectures"
              {...register('prefectures')}
            >
              <option value="1">
                北海道
              </option>
              <option value="2">
                東京
              </option>
              <option value="3">
                沖縄
              </option>
            </select>
          </div>
          <div>
            <div>
              {`変更(dirtyFields):${dirtyFields.prefectures?.toString()}`}
            </div>
            <div>
              {`操作(isTouched):${touchedFields.prefectures?.toString()}`}
            </div>
            <div>
              エラー(invalid):
            </div>
            <div>
              {`エラー情報(error):${errors.prefectures?.message}`}
            </div>
          </div>
        </div>

        <br />
        <div>
          <div>
            <label htmlFor="municipality">
              市町村
            </label>
            <input
              type="text"
              id="municipality"
              disabled={getValues('prefectures') === ''}
              onChange={onChange}
              onBlur={onBlur}
              ref={ref}
              name={name}
            />
          </div>
          <div>
            <div>
              {`変更(dirtyFields):${dirtyFields.municipality?.toString()}`}
            </div>
            <div>
              {`操作(isTouched):${touchedFields.municipality?.toString()}`}
            </div>
            <div>
              {`エラー情報(error):${errors.municipality?.message}`}
            </div>
          </div>
        </div>

        <br />
        <div>
          <button
            type="submit"
            disabled={(submitCount === 0 ? false : !isDirty || !isValid) || isSubmitting}
          >
            送信
          </button>
          {isSubmitting && <div>...送信中...</div>}
        </div>

        <br />
        <div>
          <button
            type="button"
            onClick={onReset}
          >
            リセット
          </button>
        </div>
      </form>
    </>
  );
}

export default RhfWithYup;
同一カテゴリの記事