공식 문서
https://www.react-hook-form.com/
1. React Hook Form 소개
React Hook Form은 리액트에서 폼 관리를 간편하고 효율적으로 할 수 있도록 도와주는 라이브러리입니다. 제어 컴포넌트(Control Components) 대신 비제어 컴포넌트(Uncontrolled Components) 방식을 사용하여 성능을 최적화하며, 간결한 API를 제공합니다.
주요 특징:
- 성능 최적화: 최소한의 리렌더링으로 빠른 성능 제공
- 간편한 API: 직관적인 훅 기반 API
- 유효성 검사: 다양한 유효성 검사 옵션 및 Yup, Zod 등의 라이브러리와 통합
- 타사 UI 라이브러리와의 호환성: Material-UI, Ant Design 등과 쉽게 통합
2. 설치
먼저, React 프로젝트에 React Hook Form을 설치합니다.
npm install react-hook-form
추가 설치 (유효성 검사 라이브러리와 통합 시):
npm install @hookform/resolvers yup
3. 기본 사용법
기본적인 폼을 React Hook Form을 사용하여 구현하는 방법을 알아보겠습니다.
예제: 간단한 로그인 폼
// src/components/LoginForm.jsx
import React from 'react';
import { useForm } from 'react-hook-form';
const LoginForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const onSubmit = (data) => {
console.log('로그인 데이터:', data);
// 로그인 로직 추가
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>이메일</label>
<input
type="email"
{...register('email', { required: '이메일을 입력해주세요.' })}
/>
{errors.email && <p>{errors.email.message}</p>}
</div>
<div>
<label>비밀번호</label>
<input
type="password"
{...register('password', { required: '비밀번호를 입력해주세요.' })}
/>
{errors.password && <p>{errors.password.message}</p>}
</div>
<button type="submit">로그인</button>
</form>
);
};
export default LoginForm;
설명:
- useForm 훅을 사용하여 폼 상태를 관리합니다.
- register 메서드를 사용하여 각 입력 필드를 등록합니다.
- handleSubmit 함수로 폼 제출 시 onSubmit 함수를 호출합니다.
- errors 객체를 통해 유효성 검사 에러를 처리합니다.
4. 유효성 검사
React Hook Form은 기본적인 유효성 검사 옵션을 제공하며, 외부 라이브러리(Yup, Zod 등)와 통합하여 더 복잡한 검증도 가능합니다.
기본 유효성 검사 예제:
<input
type="email"
{...register('email', {
required: '이메일을 입력해주세요.',
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: '유효한 이메일 형식이 아닙니다.',
},
})}
/>
Yup을 사용한 스키마 기반 유효성 검사:
- Yup 스키마 정의
// src/validationSchema.js
import * as Yup from 'yup';
export const loginSchema = Yup.object().shape({
email: Yup.string()
.email('유효한 이메일 주소를 입력하세요.')
.required('이메일은 필수 항목입니다.'),
password: Yup.string()
.min(6, '비밀번호는 최소 6자 이상이어야 합니다.')
.required('비밀번호는 필수 항목입니다.'),
});
React Hook Form과 통합
// src/components/LoginForm.jsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { loginSchema } from '../validationSchema';
const LoginForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: yupResolver(loginSchema),
});
const onSubmit = (data) => {
console.log('로그인 데이터:', data);
// 로그인 로직 추가
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* 이메일 필드 */}
<div>
<label>이메일</label>
<input type="email" {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}
</div>
{/* 비밀번호 필드 */}
<div>
<label>비밀번호</label>
<input type="password" {...register('password')} />
{errors.password && <p>{errors.password.message}</p>}
</div>
<button type="submit">로그인</button>
</form>
);
};
export default LoginForm;
설명:
- yupResolver를 사용하여 Yup 스키마를 React Hook Form에 통합합니다.
- 스키마에서 정의한 유효성 검사가 자동으로 적용됩니다.
5. 폼 제출 처리
폼 제출 시 데이터를 서버로 전송하고, 응답을 처리하는 방법을 알아보겠습니다.
예제: 회원 가입 폼과 서버 통신
// src/components/SignupForm.jsx
import React, { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import { yupResolver } from '@hookform/resolvers/yup';
import * as Yup from 'yup';
const SignupForm = () => {
const navigate = useNavigate();
const [serverError, setServerError] = useState('');
// Yup 스키마 정의
const validationSchema = Yup.object().shape({
username: Yup.string().required('아이디는 필수 항목입니다.'),
name: Yup.string().required('이름은 필수 항목입니다.'),
password: Yup.string()
.min(6, '비밀번호는 최소 6자 이상이어야 합니다.')
.required('비밀번호는 필수 항목입니다.'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password'), null], '비밀번호가 일치하지 않습니다.')
.required('비밀번호 확인은 필수 항목입니다.'),
birthDate: Yup.date().required('생일은 필수 항목입니다.'),
email: Yup.string()
.email('유효한 이메일 주소를 입력하세요.')
.required('이메일은 필수 항목입니다.'),
});
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
reset,
} = useForm({
resolver: yupResolver(validationSchema),
});
const onSubmit = async (data) => {
try {
setServerError('');
// API 호출
const response = await axios.post('/api/users/signup', data);
console.log('회원 가입 성공:', response.data);
// 성공 시 로그인 페이지로 이동
navigate('/login');
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response && error.response.data.message) {
setServerError(error.response.data.message);
} else {
setServerError('회원 가입 중 오류가 발생했습니다.');
}
} else {
setServerError('알 수 없는 오류가 발생했습니다.');
}
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* 아이디 필드 */}
<div>
<label>아이디</label>
<input type="text" {...register('username')} />
{errors.username && <p>{errors.username.message}</p>}
</div>
{/* 이름 필드 */}
<div>
<label>이름</label>
<input type="text" {...register('name')} />
{errors.name && <p>{errors.name.message}</p>}
</div>
{/* 비밀번호 필드 */}
<div>
<label>비밀번호</label>
<input type="password" {...register('password')} />
{errors.password && <p>{errors.password.message}</p>}
</div>
{/* 비밀번호 확인 필드 */}
<div>
<label>비밀번호 확인</label>
<input type="password" {...register('confirmPassword')} />
{errors.confirmPassword && <p>{errors.confirmPassword.message}</p>}
</div>
{/* 생일 필드 */}
<div>
<label>생일</label>
<input type="date" {...register('birthDate')} />
{errors.birthDate && <p>{errors.birthDate.message}</p>}
</div>
{/* 이메일 필드 */}
<div>
<label>이메일</label>
<input type="email" {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}
</div>
{/* 서버 에러 메시지 */}
{serverError && <p style={{ color: 'red' }}>{serverError}</p>}
{/* 제출 버튼 */}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '회원 가입 중...' : '회원 가입'}
</button>
</form>
);
};
export default SignupForm;
설명:
- 폼 제출 처리: handleSubmit를 통해 onSubmit 함수를 호출합니다.
- API 호출: axios를 사용하여 서버에 폼 데이터를 전송합니다.
- 에러 처리: 서버에서 반환된 에러 메시지를 serverError 상태에 저장하여 사용자에게 표시합니다.
- 로딩 상태: isSubmitting을 사용하여 폼 제출 중임을 표시하고, 버튼을 비활성화합니다.
6. 에러 처리
React Hook Form에서는 클라이언트 측 유효성 검사뿐만 아니라 서버 측 에러도 효과적으로 처리할 수 있습니다.
서버 측 에러 처리 예제:
import { useForm } from 'react-hook-form';
import { setError } from 'react-hook-form';
const onSubmit = async (data) => {
try {
// API 호출
const response = await axios.post('/api/users/signup', data);
// 성공 처리
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response && error.response.data.fieldErrors) {
// 필드별 에러 설정
Object.entries(error.response.data.fieldErrors).forEach(([field, message]) => {
setError(field, { type: 'server', message });
});
}
if (error.response && error.response.data.message) {
// 일반 에러 메시지 설정
setServerError(error.response.data.message);
}
}
}
};
설명:
- setError를 사용하여 특정 필드에 서버 측 에러 메시지를 설정할 수 있습니다.
- 일반 에러 메시지는 별도의 상태(serverError)에 저장하여 표시할 수 있습니다.
7. 동적 필드 관리
React Hook Form에서는 useFieldArray 훅을 사용하여 동적으로 필드를 추가하거나 제거할 수 있습니다.
예제: 동적으로 이메일 필드 추가
// src/components/DynamicEmailForm.jsx
import React from 'react';
import { useForm, useFieldArray } from 'react-hook-form';
const DynamicEmailForm = () => {
const { register, control, handleSubmit, formState: { errors } } = useForm({
defaultValues: {
emails: [{ email: '' }],
},
});
const { fields, append, remove } = useFieldArray({
control,
name: 'emails',
});
const onSubmit = (data) => {
console.log('제출된 데이터:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{fields.map((field, index) => (
<div key={field.id}>
<label>이메일 {index + 1}</label>
<input
type="email"
{...register(`emails.${index}.email`, { required: '이메일을 입력해주세요.' })}
/>
{errors.emails?.[index]?.email && (
<p>{errors.emails[index].email.message}</p>
)}
<button type="button" onClick={() => remove(index)}>
삭제
</button>
</div>
))}
<button type="button" onClick={() => append({ email: '' })}>
이메일 추가
</button>
<button type="submit">제출</button>
</form>
);
};
export default DynamicEmailForm;
설명:
- useFieldArray를 사용하여 동적 필드를 관리합니다.
- append 함수로 새로운 필드를 추가하고, remove 함수로 필드를 제거할 수 있습니다.
- 각 필드는 고유한 key를 가져야 합니다 (field.id 사용).
8. 커스텀 컴포넌트와 통합
타사 UI 라이브러리나 커스텀 컴포넌트와 React Hook Form을 통합하는 방법을 알아보겠습니다.
예제: Material-UI의 TextField와 통합
- 설치
npm install @mui/material @emotion/react @emotion/styled
통합 예제
// src/components/MaterialUITextFieldForm.jsx
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
const MaterialUITextFieldForm = () => {
const { control, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => {
console.log('제출된 데이터:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* 이메일 필드 */}
<Controller
name="email"
control={control}
defaultValue=""
rules={{
required: '이메일을 입력해주세요.',
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: '유효한 이메일 형식이 아닙니다.',
},
}}
render={({ field }) => (
<TextField
{...field}
label="이메일"
variant="outlined"
error={!!errors.email}
helperText={errors.email ? errors.email.message : ''}
fullWidth
margin="normal"
/>
)}
/>
{/* 비밀번호 필드 */}
<Controller
name="password"
control={control}
defaultValue=""
rules={{
required: '비밀번호를 입력해주세요.',
minLength: {
value: 6,
message: '비밀번호는 최소 6자 이상이어야 합니다.',
},
}}
render={({ field }) => (
<TextField
{...field}
type="password"
label="비밀번호"
variant="outlined"
error={!!errors.password}
helperText={errors.password ? errors.password.message : ''}
fullWidth
margin="normal"
/>
)}
/>
<Button type="submit" variant="contained" color="primary">
제출
</Button>
</form>
);
};
export default MaterialUITextFieldForm;
설명:
- Controller 컴포넌트를 사용하여 비제어 컴포넌트(예: Material-UI의 TextField)를 React Hook Form과 통합합니다.
- rules를 사용하여 유효성 검사를 설정합니다.
- render prop을 통해 커스텀 컴포넌트를 렌더링하면서 폼 필드를 연결합니다.














댓글 ( 0)
댓글 남기기