공식 문서
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)
댓글 남기기