react-form-hook
을 사용해서 폼을 관리하고 검증 로직까지 구현해보자!
React에서 폼을 쉽게 관리하고 유효성 검사를 효율적으로 처리할 수 있도록 도와주는 라이브러리
useForm
훅을 사용하여 폼을 초기화하고, register
를 통해 각 폼 필드를 간단하게 관리할 수 있다.간단 예시
import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
// 폼 데이터의 타입을 정의합니다.
interface FormSchema {
username: string;
password: string;
}
function MyForm() {
// useForm 훅을 사용하여 폼을 초기화합니다.
const { register, handleSubmit, formState: { errors } } = useForm<FormSchema>();
// 제출 핸들러를 정의합니다.
const onSubmit: SubmitHandler<FormSchema> = data => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="username">Username</label>
<input
id="username"
{...register("username", { required: "Username is required" })}
placeholder="Username"
/>
{errors.username && <p>{errors.username.message}</p>}
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
{...register("password", { required: "Password is required" })}
placeholder="Password"
/>
{errors.password && <p>{errors.password.message}</p>}
</div>
<button type="submit">Submit</button>
</form>
);
}
export default MyForm;
1. use~Form() 커스텀 훅
use~Form()
커스텀 훅을 정의해서 사용한다.
import { useForm } from 'react-hook-form';
import { emailValidation, passwordConfirmValidation, passwordValidation } from './validate';
export interface SignupFormSchema {
email: string;
password: string;
passwordConfirm: string;
}
export function useSignupForm() {
const { register, handleSubmit, watch, formState } = useForm<SignupFormSchema>();
return {
register: {
email: register('email', emailValidation),
password: register('password', passwordValidation),
passwordConfirm: register('passwordConfirm', passwordConfirmValidation(watch('password'))),
},
handleSubmit,
watch,
formState,
};
}
2. validation 로직
난 아래처럼 작성했다. 오류 메세지 등등 상수는 따로 작성
import { EASY_STRINGS, VALIDATION_MESSAGES } from '../model/constants';
export const requiredValidation = {
required: VALIDATION_MESSAGES.REQUIRED,
};
export const emailValidation = {
...requiredValidation,
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: VALIDATION_MESSAGES.INVALID_EMAIL,
},
};
export const passwordValidation = {
...requiredValidation,
minLength: {
value: 8,
message: VALIDATION_MESSAGES.MIN_LENGTH(8),
},
validate: {
vaildateStrength: (value: string) => {
const matches = [/[A-Z]/.test(value), /[a-z]/.test(value), /[0-9]/.test(value), /[!@#$%^&*]/.test(value)];
return matches.filter(Boolean).length > 3 || VALIDATION_MESSAGES.STRONG_PASSWORD;
},
notEasyString: (value: string) =>
!EASY_STRINGS.some((easyString) => value.includes(easyString)) || VALIDATION_MESSAGES.EASY_PASSWORD,
},
};
export const passwordConfirmValidation = (password: string) => ({
...requiredValidation,
validate: (value: string) => value === password || VALIDATION_MESSAGES.PASSWORD_MISMATCH,
});
3. Form 컴포넌트
‘use client';
를 작성해서 클라이언트 컴포넌트로 만들어준다.Input
컴포넌트를 정의해서 사용했다.'use client';
import { Button, Input, LinkButton, Logo, Modal } from '@/shard/ui';
import { useSignupForm } from '../lib';
import type { SignupFormSchema } from '../lib';
export interface SignupFormProps {}
function SignupForm({}: SignupFormProps) {
const { register, handleSubmit, formState } = useSignupForm();
const onSubmit = (data: SignupFormSchema) => {
console.log('Form Data:', data);
// 여기에 회원가입 로직을 추가하세요.UseFormReturn
};
return (
<Modal>
<div className='flex-center flex flex-col gap-48'>
<Logo />
<div className='flex-center flex flex-col gap-16'>
<form className='flex-center flex flex-col gap-36' onSubmit={handleSubmit(onSubmit)}>
<div className='flex-center flex flex-col gap-8'>
<Input
id='email'
label='이메일'
type='text'
{...register.email}
error={formState.errors.email?.message}
/>
<Input
id='password'
label='비밀번호'
type='password'
{...register.password}
error={formState.errors.password?.message}
/>
<Input
id='passwordConfirm'
label='비밀번호 확인'
type='password'
{...register.passwordConfirm}
error={formState.errors.passwordConfirm?.message}
/>
</div>
<Button type='submit'>회원 가입</Button>
</form>
<LinkButton href='/login'>로그인하기</LinkButton>
</div>
</div>
</Modal>
);
}
export default SignupForm;
4. 커스텀 Input 컴포넌트
import cn from 'classnames';
import { InputHTMLAttributes, forwardRef } from 'react';
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
id: string;
label: string;
error?: string;
}
const Input = forwardRef<HTMLInputElement, InputProps>(({ id, label, error, ...props }: InputProps, ref) => (
<div className='flex text-13 text-neutral'>
<label className={cn('h-30 w-100 font-semibold', { 'text-red-500': error })} htmlFor={id}>
{label}
</label>
<div>
<input
id={id}
ref={ref} // ref 전달
className={cn('input input-bordered mb-4 h-30 w-250 text-14', { 'input-error': error })}
{...props} // 나머지 props 전달
/>
<div className={cn('text-10 text-red-500', { hidden: !error })}>⚠ {error}</div>
</div>
</div>
));
Input.displayName = 'Input';
export default Input;
끝!