회원가입 폼 만들기
이 실습에서는 react-hook-form을 사용하여 완전한 회원가입 폼을 구현해보겠습니다.
이 과정을 통해 폼 처리, 유효성 검사, 접근성, 그리고 사용자 경험 향상 기법 등을 종합적으로 적용해볼 수 있습니다.
1단계 : 프로젝트 설정
먼저 필요한 의존성을 설치합니다.
npm install react-hook-form @hookform/resolvers yup
여기서 yup
은 스키마 기반 유효성 검사를 위해 사용됩니다.
2단계 : 회원가입 폼 컴포넌트 생성
SignupForm.js
파일을 생성하고 기본 구조를 작성합니다.
import React from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
function SignupForm() {
// 폼 로직은 여기에 구현됩니다
return (
<form>
{/* 폼 필드들은 여기에 구현됩니다 */}
</form>
);
}
export default SignupForm;
3단계 : 유효성 검사 스키마 정의
yup을 사용하여 유효성 검사 스키마를 정의합니다.
const schema = yup.object().shape({
name: yup.string().required('이름은 필수입니다'),
email: yup.string().email('유효한 이메일 주소를 입력하세요').required('이메일은 필수입니다'),
password: yup.string()
.min(8, '비밀번호는 최소 8자 이상이어야 합니다')
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
'비밀번호는 대소문자, 숫자, 특수문자를 포함해야 합니다')
.required('비밀번호는 필수입니다'),
confirmPassword: yup.string()
.oneOf([yup.ref('password'), null], '비밀번호가 일치하지 않습니다')
.required('비밀번호 확인은 필수입니다'),
});
4단계 : useForm 훅 설정
useForm 훅을 사용하여 폼 상태를 관리합니다.
function SignupForm() {
const { register, handleSubmit, formState: { errors }, watch } = useForm({
resolver: yupResolver(schema)
});
const onSubmit = async (data) => {
try {
// API 호출 로직
console.log('Form submitted:', data);
// 성공 처리
} catch (error) {
// 오류 처리
}
};
// ... 폼 JSX
}
5단계 : 폼 필드 구현
각 폼 필드를 구현하고 유효성 검사를 연결합니다.
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="name">이름</label>
<input
id="name"
{...register('name')}
aria-invalid={errors.name ? "true" : "false"}
/>
{errors.name && <span role="alert">{errors.name.message}</span>}
</div>
<div>
<label htmlFor="email">이메일</label>
<input
id="email"
type="email"
{...register('email')}
aria-invalid={errors.email ? "true" : "false"}
/>
{errors.email && <span role="alert">{errors.email.message}</span>}
</div>
<div>
<label htmlFor="password">비밀번호</label>
<input
id="password"
type="password"
{...register('password')}
aria-invalid={errors.password ? "true" : "false"}
/>
{errors.password && <span role="alert">{errors.password.message}</span>}
</div>
<div>
<label htmlFor="confirmPassword">비밀번호 확인</label>
<input
id="confirmPassword"
type="password"
{...register('confirmPassword')}
aria-invalid={errors.confirmPassword ? "true" : "false"}
/>
{errors.confirmPassword && <span role="alert">{errors.confirmPassword.message}</span>}
</div>
<button type="submit">가입하기</button>
</form>
);
6단계 : 비밀번호 강도 체크 기능 추가
비밀번호 입력 필드 아래에 비밀번호 강도를 표시하는 컴포넌트를 추가합니다.
function PasswordStrengthMeter({ password }) {
const getPasswordStrength = (password) => {
let strength = 0;
if (password.length >= 8) strength++;
if (password.match(/[a-z]+/)) strength++;
if (password.match(/[A-Z]+/)) strength++;
if (password.match(/[0-9]+/)) strength++;
if (password.match(/[$@#&!]+/)) strength++;
return strength;
};
const strength = getPasswordStrength(password);
return (
<div className="password-strength-meter">
<div className={`strength-${strength}`}></div>
<span>비밀번호 강도: {['매우 약함', '약함', '보통', '강함', '매우 강함'][strength]}</span>
</div>
);
}
// SignupForm 컴포넌트 내부
const password = watch('password');
// ...
<PasswordStrengthMeter password={password} />
7단계 : 폼 제출 후 처리
폼 제출 후 성공 또는 실패 메시지를 표시하는 로직을 추가합니다.
function SignupForm() {
// ...이전 코드...
const [submitStatus, setSubmitStatus] = useState(null);
const onSubmit = async (data) => {
try {
// API 호출 시뮬레이션
await new Promise(resolve => setTimeout(resolve, 1000));
setSubmitStatus('success');
} catch (error) {
setSubmitStatus('error');
}
};
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
{/* ...폼 필드... */}
</form>
{submitStatus === 'success' && <div role="alert">회원가입에 성공했습니다!</div>}
{submitStatus === 'error' && <div role="alert">회원가입 중 오류가 발생했습니다. 다시 시도해주세요.</div>}
</>
);
}
8단계 : 접근성 개선
폼의 접근성을 개선하기 위해 추가적인 ARIA 속성을 적용합니다.
<form onSubmit={handleSubmit(onSubmit)} noValidate aria-label="회원가입 폼">
{/* ...폼 필드... */}
<div role="status" aria-live="polite">
{submitStatus === 'success' && "회원가입에 성공했습니다!"}
{submitStatus === 'error' && "회원가입 중 오류가 발생했습니다. 다시 시도해주세요."}
</div>
</form>
완성된 회원가입 폼 코드
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
const schema = yup.object().shape({
name: yup.string().required('이름은 필수입니다'),
email: yup.string().email('유효한 이메일 주소를 입력하세요').required('이메일은 필수입니다'),
password: yup.string()
.min(8, '비밀번호는 최소 8자 이상이어야 합니다')
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
'비밀번호는 대소문자, 숫자, 특수문자를 포함해야 합니다')
.required('비밀번호는 필수입니다'),
confirmPassword: yup.string()
.oneOf([yup.ref('password'), null], '비밀번호가 일치하지 않습니다')
.required('비밀번호 확인은 필수입니다'),
});
function PasswordStrengthMeter({ password }) {
// ... (이전에 정의한 대로)
}
function SignupForm() {
const { register, handleSubmit, formState: { errors }, watch } = useForm({
resolver: yupResolver(schema)
});
const [submitStatus, setSubmitStatus] = useState(null);
const onSubmit = async (data) => {
try {
await new Promise(resolve => setTimeout(resolve, 1000)); // API 호출 시뮬레이션
setSubmitStatus('success');
} catch (error) {
setSubmitStatus('error');
}
};
const password = watch('password');
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate aria-label="회원가입 폼">
<div>
<label htmlFor="name">이름</label>
<input
id="name"
{...register('name')}
aria-invalid={errors.name ? "true" : "false"}
/>
{errors.name && <span role="alert">{errors.name.message}</span>}
</div>
<div>
<label htmlFor="email">이메일</label>
<input
id="email"
type="email"
{...register('email')}
aria-invalid={errors.email ? "true" : "false"}
/>
{errors.email && <span role="alert">{errors.email.message}</span>}
</div>
<div>
<label htmlFor="password">비밀번호</label>
<input
id="password"
type="password"
{...register('password')}
aria-invalid={errors.password ? "true" : "false"}
/>
{errors.password && <span role="alert">{errors.password.message}</span>}
<PasswordStrengthMeter password={password} />
</div>
<div>
<label htmlFor="confirmPassword">비밀번호 확인</label>
<input
id="confirmPassword"
type="password"
{...register('confirmPassword')}
aria-invalid={errors.confirmPassword ? "true" : "false"}
/>
{errors.confirmPassword && <span role="alert">{errors.confirmPassword.message}</span>}
</div>
<button type="submit">가입하기</button>
<div role="status" aria-live="polite">
{submitStatus === 'success' && "회원가입에 성공했습니다!"}
{submitStatus === 'error' && "회원가입 중 오류가 발생했습니다. 다시 시도해주세요."}
</div>
</form>
);
}
export default SignupForm;
이 실습을 통해 우리는 react-hook-form을 사용하여 완전한 회원가입 폼을 구현했습니다.
이 폼은 유효성 검사, 실시간 피드백, 비밀번호 강도 체크, 접근성 고려, 그리고 제출 후 처리 등 실제 애플리케이션에서 필요한 다양한 기능을 포함하고 있습니다.
주요 특징
- yup을 사용한 스키마 기반 유효성 검사
- 실시간 에러 메시지 표시
- 비밀번호 강도 체크 기능
- 접근성을 고려한 ARIA 속성 사용
- 폼 제출 후 성공/실패 처리
이 예제는 실제 프로젝트에서 사용할 수 있는 기본적인 틀을 제공하며, 필요에 따라 추가적인 기능(예 : 이메일 인증, CAPTCHA 등)을 구현하여 확장할 수 있습니다.
또한, 스타일링을 추가하여 사용자 인터페이스를 개선하고, 서버 측 유효성 검사와 연동하여 보안을 강화할 수 있습니다.