ユースケース
例えばメールアドレスを使った新規登録画面
この場合、タイプミスなどで自分が意図していないパスワードが設定されてしまうのを防ぐために、二度同じパスワードを入力させるのが一般的です。
バックエンドのリクエストバリデーションで使われがちなclass-validator
はRHFでも使える
class-validator
といえば、NestJSで使われるバリデーションですが、実はreact-hook-formのresolverに使うこともできます。
SignupPage.tsx
import { classValidatorResolver } from '@hookform/resolvers/class-validator';
import { RegisterUserRequestDTO } from '../DTO';
import { useForm } from 'react-hook-form';
import { classValidatorResolver } from '@hookform/resolvers/class-validator';
export const SignupPage = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<RegisterUserRequestDTO>({ resolver: classValidatorResolver(RegisterUserRequestDTO) });
// JSXは省略します
};
使い方のイメージ
IsEqual()
というカスタムデコレーターを作ります。
呼び出す時はこんな感じで、第一引数に参照したいプロパティ名を指定してあげます。
RegisterUserRequestDTO.ts
import { IsEmail, IsNotEmpty, Matches, MinLength } from 'class-validator';
import { VALIDATION_ERRORS } from './error.constants';
import { isEqual } from './CustomValidator/isEqual';
export class RegisterUserRequestDTO {
@IsEmail({}, { message: VALIDATION_ERRORS.INVALID_EMAIL })
@IsNotEmpty({ message: VALIDATION_ERRORS.REQUIRED })
email: string;
@Matches(/[a-z]/, { message: VALIDATION_ERRORS.INVALID_PASSWORD })
@Matches(/[A-Z]/, { message: VALIDATION_ERRORS.INVALID_PASSWORD })
@Matches(/[0-9]/, { message: VALIDATION_ERRORS.INVALID_PASSWORD })
@MinLength(8, { message: VALIDATION_ERRORS.INVALID_PASSWORD_LENGTH })
@IsNotEmpty({ message: VALIDATION_ERRORS.REQUIRED })
password: string;
// 今回実装するのはこの `isEqual()`
@isEqual('password', { message: VALIDATION_ERRORS.NOT_MATCH_PASSWORD })
@IsNotEmpty({ message: VALIDATION_ERRORS.REQUIRED })
passwordConfirm: string;
}
IsEqual()の実装
細かい説明は省きますがこんな感じ。公式ドキュメントを参照すれば誰でも作れますね。
CustmoValidator/isEqual.ts
import { ValidateBy, ValidationArguments, ValidationOptions } from 'class-validator';
export function isEqual(property: string, validationOptions?: ValidationOptions): PropertyDecorator {
return ValidateBy(
{
name: 'isEqual',
constraints: [property],
validator: {
validate(value, args: ValidationArguments): boolean {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
return relatedValue === value;
},
},
},
validationOptions || { message: `require to match with ${property}` },
);
}