반응형
소개
폼(Form) 관리는 웹 개발에서 매우 중요한 부분입니다. React에서 react-hook-form
을 사용하면 복잡한 폼 상태 관리와 유효성 검사를 쉽게 구현할 수 있습니다. 이번 글에서는 회원가입 폼을 예제로 react-hook-form의 사용법을 자세히 알아보겠습니다.
react-hook-form 설치
npm install react-hook-form
// or
yarn add react-hook-form
기본 회원가입 폼 구현
import { useForm } from 'react-hook-form';
import { useState } from 'react';
// 폼 데이터 타입 정의
interface SignUpFormData {
email: string;
password: string;
passwordConfirm: string;
name: string;
age: number;
terms: boolean;
}
export default function SignUpForm() {
const {
register,
handleSubmit,
watch,
formState: { errors }
} = useForm<SignUpFormData>();
const onSubmit = (data: SignUpFormData) => {
console.log('폼 제출 데이터:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow">
<h2 className="text-2xl font-bold mb-6">회원가입</h2>
{/* 이메일 입력 필드 */}
<div className="mb-4">
<label className="block mb-2">이메일</label>
<input
{...register('email', {
required: '이메일은 필수입니다',
pattern: {
value: /\S+@\S+\.\S+/,
message: '이메일 형식이 올바르지 않습니다'
}
})}
className="w-full p-2 border rounded"
/>
{errors.email && <p className="text-red-500 mt-1">{errors.email.message}</p>}
</div>
{/* 비밀번호 입력 필드 */}
<div className="mb-4">
<label className="block mb-2">비밀번호</label>
<input
type="password"
{...register('password', {
required: '비밀번호는 필수입니다',
minLength: {
value: 8,
message: '비밀번호는 8자 이상이어야 합니다'
},
pattern: {
value: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/,
message: '비밀번호는 문자, 숫자, 특수문자를 포함해야 합니다'
}
})}
className="w-full p-2 border rounded"
/>
{errors.password && <p className="text-red-500 mt-1">{errors.password.message}</p>}
</div>
{/* 비밀번호 확인 필드 */}
<div className="mb-4">
<label className="block mb-2">비밀번호 확인</label>
<input
type="password"
{...register('passwordConfirm', {
required: '비밀번호 확인은 필수입니다',
validate: (value) =>
value === watch('password') || '비밀번호가 일치하지 않습니다'
})}
className="w-full p-2 border rounded"
/>
{errors.passwordConfirm && (
<p className="text-red-500 mt-1">{errors.passwordConfirm.message}</p>
)}
</div>
{/* 이름 입력 필드 */}
<div className="mb-4">
<label className="block mb-2">이름</label>
<input
{...register('name', {
required: '이름은 필수입니다',
minLength: {
value: 2,
message: '이름은 2자 이상이어야 합니다'
}
})}
className="w-full p-2 border rounded"
/>
{errors.name && <p className="text-red-500 mt-1">{errors.name.message}</p>}
</div>
{/* 나이 입력 필드 */}
<div className="mb-4">
<label className="block mb-2">나이</label>
<input
type="number"
{...register('age', {
required: '나이는 필수입니다',
min: {
value: 14,
message: '14세 이상만 가입 가능합니다'
}
})}
className="w-full p-2 border rounded"
/>
{errors.age && <p className="text-red-500 mt-1">{errors.age.message}</p>}
</div>
{/* 약관 동의 체크박스 */}
<div className="mb-6">
<label className="flex items-center">
<input
type="checkbox"
{...register('terms', {
required: '약관 동의는 필수입니다'
})}
className="mr-2"
/>
<span>이용약관에 동의합니다</span>
</label>
{errors.terms && <p className="text-red-500 mt-1">{errors.terms.message}</p>}
</div>
{/* 제출 버튼 */}
<button
type="submit"
className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600"
>
가입하기
</button>
</form>
);
}
커스텀 유효성 검사 추가
const validatePassword = (password: string) => {
const hasLower = /[a-z]/.test(password);
const hasUpper = /[A-Z]/.test(password);
const hasNumber = /\d/.test(password);
const hasSpecial = /[@$!%*#?&]/.test(password);
if (!hasLower) return '소문자를 포함해야 합니다';
if (!hasUpper) return '대문자를 포함해야 합니다';
if (!hasNumber) return '숫자를 포함해야 합니다';
if (!hasSpecial) return '특수문자를 포함해야 합니다';
return true;
};
// 비밀번호 필드에 적용
{...register('password', {
validate: validatePassword
})}
비동기 유효성 검사 추가
// 이메일 중복 체크 함수
const checkEmailDuplicate = async (email: string) => {
try {
const response = await fetch(`/api/check-email?email=${email}`);
const data = await response.json();
return !data.exists || '이미 사용중인 이메일입니다';
} catch (error) {
console.error('이메일 체크 오류:', error);
return '이메일 확인에 실패했습니다';
}
};
// 이메일 필드에 적용
{...register('email', {
validate: {
duplicate: checkEmailDuplicate
}
})}
폼 데이터 처리와 제출
const onSubmit = async (data: SignUpFormData) => {
try {
const response = await fetch('/api/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error('회원가입 실패');
}
// 성공 처리
alert('회원가입이 완료되었습니다!');
// 로그인 페이지로 이동
router.push('/login');
} catch (error) {
console.error('회원가입 오류:', error);
alert('회원가입 중 오류가 발생했습니다.');
}
};
폼 상태 관리
const {
formState: { isSubmitting, isDirty, isValid }
} = useForm<SignUpFormData>({
mode: 'onChange' // 실시간 유효성 검사
});
// 제출 버튼 상태 처리
<button
type="submit"
disabled={!isDirty || !isValid || isSubmitting}
className={`w-full p-2 rounded
${!isDirty || !isValid || isSubmitting
? 'bg-gray-300 cursor-not-allowed'
: 'bg-blue-500 hover:bg-blue-600'}`}
>
{isSubmitting ? '처리중...' : '가입하기'}
</button>
에러 메시지 커스터마이징
const errorMessages = {
email: {
required: '이메일을 입력해주세요',
pattern: '올바른 이메일 형식이 아닙니다'
},
password: {
required: '비밀번호를 입력해주세요',
minLength: '비밀번호는 8자 이상이어야 합니다'
}
};
// 에러 메시지 표시 컴포넌트
const ErrorMessage = ({ error }: { error: any }) => {
if (!error) return null;
return (
<p className="text-red-500 text-sm mt-1">
{error.message}
</p>
);
};
결론
react-hook-form을 사용하면 복잡한 폼 관리와 유효성 검사를 쉽게 구현할 수 있습니다. 특히 타입스크립트와 함께 사용하면 더욱 안전하고 견고한 폼을 만들 수 있습니다. 실시간 유효성 검사, 커스텀 검증 로직, 비동기 검증 등 다양한 기능을 활용하여 사용자 친화적인 회원가입 폼을 구현해보세요.
'TypeScript' 카테고리의 다른 글
3D 카드 플립 효과로 배우는 실전 Tailwind 활용법 (0) | 2024.11.20 |
---|---|
[Next.js 실전] Image 컴포넌트로 웹 성능 최적화하기 (0) | 2024.11.18 |
[React 최적화] 메모이제이션 완벽 가이드 - useMemo, useCallback, React.memo 실전 활용법 (0) | 2024.11.17 |
데이터베이스 API 구현하기 : SQLite와 타입 안전성 (7) | 2024.11.17 |
React에서 Tailwind CSS 활용하기 (1) | 2024.11.15 |