
react-hook-form 을 통한 회원가입 모달 내 유효성 검사 진행💁🏻♀️ 무엇을 할 것이냐면?
1. 각 입력값이 유효한 (검증을 마친) 상태면input-success클래스 부여 (tailwindCSS)
2. 사용자가 입력 중이거나 유효하지 않은 (잘못된) 값이라면input-error클래스 부여
3. 각error message는 input 하단에 출력
4. 모든 form field 가 검증을 마친 상태라면 submit 버튼 활성화
//비밀번호 Input
<input
type='password'
placeholder='password'
{...register('password', {
required: '필수 입력 항목입니다',
pattern: {
value:
/^.*(?=^.{8,16}$)(?=.*\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&+=]).*$/,
message: '8-16자 이내, 영문, 숫자, 특수 문자를 포함해주세요',
},
})}
/>
pattern : 정규표현식을 이용한 유효성 검사 진행, message는 error.message 로 출력된다required : 입력한 메세지는 error.message 로 출력된다비밀번호 확인의 경우 비밀번호 입력값과 일치한 지 검증해야 하는데 이 때에는 validate 활용
validate (문서 번역) 콜백 함수를 인수로 전달하여 유효성을 검사할 수도 있고, 콜백 함수의 개체를 전달하여 모든 유효성을 검사할 수도 있습니다. 이 함수는 속성에 포함된 다른 유효성 검사 규칙에 의존하지 않고 자체적으로 실행됩니다 ... 중략
사실 처음에 공식 문서만 봐서는 정확히 감이 오지 않았지만, 활용해 검증한 많은 케이스들을 보면서 익혔다. 문자열을 return할 경우 이 역시 error.message 로 출력된다
//비밀번호 확인 Input
<input
type='password'
placeholder='verify password'
{...register('verifyPwd', {
required: '필수 입력 항목입니다',
validate: {
value: (value) => {
const { password } = getValues()
return password === value || '비밀번호가 일치하지 않습니다'
},
},
})}
/>
getFieldState() 메서드에 인수로 name 전달 시 반환되는 객체에는 4가지 속성이 있다

이 중
1. 사용자가 입력을 시도했나
2. 값이 유효한가
두 상태를 알아야 하기 때문에 isTouched (해당 필드에 focus / blue 이벤트 발생 여부 확인) 와 invalid 활용
field 상태에 따른 클래스명 부여를 할 계획이었으므로 위의 두 조건을 검증하는 함수를 따로 하나 만들었다 (코드 중복 방지)
type Formvalues = { name: string email: string password: string verifyPwd: string } //중략 const isItValidCSS = (name: keyof Formvalues) => { return !getFieldState(name).isTouched ? '' : getFieldState(name).invalid ? 'input-error' : 'input-success' }
- isItValidCSS은 인자로 field name : string 을 받고 문자열을 리턴하는 함수다
입력 전이라면빈 문자열 return입력중이거나 유효하지 않은상태라면 input-error입력중이거나 유효한상태라면 input-success
<input
type='password'
placeholder='password'
{...register('password', {
required: '필수 입력 항목입니다',
pattern: {
value:
/^.*(?=^.{8,16}$)(?=.*\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&+=]).*$/,
message: '8-16자 이내, 영문, 숫자, 특수 문자를 포함해주세요',
},
})}
className={clsx(`input input-bordered mb-1.5`,
isItValidCSS('password') )} // 문자열 return
/>
isValid 을 통해 확인이 가능하다 const {
register,
getValues,
handleSubmit,
getFieldState,
formState: { errors, isValid },
} = useForm<Formvalues>({ mode: 'all' })
isValid : 모든 검증이 마치면 true 상태로 변환 //submit 버튼
<button type='submit' disabled={!isValid} className='btn btn-primary'>
Create one
</button>
끝!
//signUpModal.tsx
import clsx from 'clsx'
import { SubmitHandler, useForm } from 'react-hook-form'
import WarningMsg from '../common/warningMsg'
type Formvalues = {
name: string
email: string
password: string
confirmPwd: string
}
const SignUpModal = () => {
const {
register,
getValues,
handleSubmit,
getFieldState,
formState: { errors, isValid },
} = useForm<Formvalues>({ mode: 'all' })
const onSubmitHandler: SubmitHandler<Formvalues> = (e: any) => {
console.log(e)
}
const isItValidCSS = (type: keyof Formvalues) => {
return !getFieldState(type).isDirty
? ''
: getFieldState(type).invalid
? 'input-error'
: 'input-success'
}
return (
<form className='card-body' onSubmit={handleSubmit(onSubmitHandler)}>
<h2 className='text-center text-xl'>회원가입</h2>
<div className='form-control'>
<label className='label'>
<span className='label-text'>Name</span>
</label>
<input
type='text'
placeholder='name'
{...register('name', {
required: '필수 입력 항목입니다',
})}
className={clsx(`input input-bordered mb-1.5`, isItValidCSS('name'))}
required
/>
{errors.name && errors.name.message && (
<WarningMsg message={errors.name.message} />
)}
</div>
<div className='form-control'>
<label className='label'>
<span className='label-text'>Email</span>
</label>
<input
type='email'
placeholder='email'
{...register('email', {
required: '필수 입력 항목입니다',
pattern: {
value: /^[a-z0-9]+@[a-z]+\.[a-z]{2,3}$/i,
message: '유효한 이메일 형식이 아닙니다',
},
})}
className={clsx(`input input-bordered mb-1.5`, isItValidCSS('email'))}
/>
{errors.email && errors.email.message && (
<WarningMsg message={errors.email.message} />
)}
</div>
<div className='form-control'>
<label className='label'>
<span className='label-text'>Password</span>
</label>
<input
type='password'
placeholder='password'
{...register('password', {
required: '필수 입력 항목입니다',
pattern: {
value:
/^.*(?=^.{8,16}$)(?=.*\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&+=]).*$/,
message: '8-16자 이내, 영문, 숫자, 특수 문자를 포함해주세요',
},
})}
className={clsx(
`input input-bordered mb-1.5`,
isItValidCSS('password')
)}
/>
{errors.password && errors.password.message && (
<WarningMsg message={errors.password.message} />
)}
</div>
<div className='form-control'>
<label className='label'>
<span className='label-text'>Confirm Password</span>
</label>
<input
type='password'
placeholder='confirm password'
{...register('confirmPwd', {
required: '필수 입력 항목입니다',
validate: {
value: (value) => {
const { password } = getValues()
return password === value || '비밀번호가 일치하지 않습니다'
},
},
})}
className={clsx(
`input input-bordered mb-1.5`,
isItValidCSS('confirmPwd')
)}
/>
{errors.confirmPwd && errors.confirmPwd.message && (
<WarningMsg message={errors.confirmPwd.message} />
)}
</div>
<div className='form-control mt-6'>
<button disabled={!isValid} className='btn btn-primary'>
Create one
</button>
</div>
</form>
)
}
export default SignUpModal