React
React 폼 처리 완벽 가이드
React에서 폼을 효과적으로 다루는 방법. 제어 컴포넌트, 유효성 검사, React Hook Form 활용법을 알아봅니다.
폼은 웹 애플리케이션에서 사용자 입력을 받는 핵심 요소입니다. React에서 폼을 효과적으로 다루는 방법을 알아봅니다.
제어 컴포넌트
React 상태로 폼 값을 관리합니다.
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="이메일"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="비밀번호"
/>
<button type="submit">로그인</button>
</form>
);
}
여러 필드 처리
function SignupForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
confirmPassword: '',
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
return (
<form>
<input
name="name"
value={formData.name}
onChange={handleChange}
/>
<input
name="email"
value={formData.email}
onChange={handleChange}
/>
{/* ... */}
</form>
);
}
유효성 검사
function SignupForm() {
const [formData, setFormData] = useState({ email: '', password: '' });
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (!formData.email) {
newErrors.email = '이메일을 입력하세요';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = '올바른 이메일 형식이 아닙니다';
}
if (!formData.password) {
newErrors.password = '비밀번호를 입력하세요';
} else if (formData.password.length < 8) {
newErrors.password = '비밀번호는 8자 이상이어야 합니다';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
// 제출 로직
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
/>
{errors.email && <span className="error">{errors.email}</span>}
</div>
{/* ... */}
</form>
);
}
React Hook Form
대규모 폼에서는 라이브러리를 사용하는 것이 효율적입니다.
import { useForm } from 'react-hook-form';
function SignupForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
watch,
} = useForm();
const onSubmit = async (data) => {
await submitForm(data);
};
const password = watch('password');
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<input
{...register('email', {
required: '이메일을 입력하세요',
pattern: {
value: /\S+@\S+\.\S+/,
message: '올바른 이메일 형식이 아닙니다',
},
})}
type="email"
/>
{errors.email && <span>{errors.email.message}</span>}
</div>
<div>
<input
{...register('password', {
required: '비밀번호를 입력하세요',
minLength: {
value: 8,
message: '비밀번호는 8자 이상이어야 합니다',
},
})}
type="password"
/>
{errors.password && <span>{errors.password.message}</span>}
</div>
<div>
<input
{...register('confirmPassword', {
required: '비밀번호 확인을 입력하세요',
validate: (value) =>
value === password || '비밀번호가 일치하지 않습니다',
})}
type="password"
/>
{errors.confirmPassword && <span>{errors.confirmPassword.message}</span>}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '처리 중...' : '가입하기'}
</button>
</form>
);
}
Zod와 함께 사용
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
email: z.string().email('올바른 이메일 형식이 아닙니다'),
password: z.string().min(8, '비밀번호는 8자 이상이어야 합니다'),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: '비밀번호가 일치하지 않습니다',
path: ['confirmPassword'],
});
function SignupForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
});
// ...
}
폼 UX 개선
실시간 유효성 검사
const { register, formState: { errors, touchedFields } } = useForm({
mode: 'onBlur', // 포커스 벗어날 때 검사
// mode: 'onChange', // 입력할 때마다 검사
});
// 터치된 필드만 에러 표시
{touchedFields.email && errors.email && (
<span>{errors.email.message}</span>
)}
로딩 상태 처리
function Form() {
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
try {
await submitData();
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit}>
{/* 입력 필드들 */}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? (
<>
<Spinner /> 처리 중...
</>
) : (
'제출'
)}
</button>
</form>
);
}
에러 메시지 스타일링
.field-error {
color: #dc2626;
font-size: 0.875rem;
margin-top: 0.25rem;
}
.input-error {
border-color: #dc2626;
}
.input-error:focus {
outline-color: #dc2626;
}
마무리
간단한 폼은 useState로 충분하지만, 복잡한 폼에서는 React Hook Form 같은 라이브러리가 효율적입니다.
사용자 경험을 위해 실시간 유효성 검사, 명확한 에러 메시지, 로딩 상태 표시를 신경 쓰면 좋습니다.