環境
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;