[React] React-Hook-Form 으로 폼(Form)을 구현하자 ‼️
![[React] React-Hook-Form 으로 폼(Form)을 구현하자 ‼️](https://cdn.hashnode.com/res/hashnode/image/upload/v1748417300999/22c70660-ca12-467e-87fa-8834ad0e7808.jpeg)
왜 React-Hook-Form 사용을 결정하게 되었는지 ? 👇🏻
저번 프로젝트에서도 반려동물 정보 입력 페이지를 담당 했었는데 그때는 리액트 훅 폼을 사용하지 않았다..!
그러다보니 모든 입력 필드를 useState로 관리했고 코드도 어마무시하게 길어졌다. (입력 + 수정 + 추가) 페이지 코드가 모두 260줄이 넘는 이슈…!! 이게 웃긴게 그 당시 하면서 느꼈지만 입력 + 수정 + 추가 모두 같은 폼을 사용하고 있다.(+같은 코드)
그때도 그랬지만 지금 생각해보면 말이 더 안 된다…🙂↔️(부정)


추가적으로 에러랑 예외처리가 힘들었다. 유효성 검사는 petValidation.js파일을 utils로 분리해둬서 사용했고 에러를 처리하는 방식은 조건문 또는 별도 상태로 에러 추적해줬다..😾
비효율이적이다.. 굉장히..엄청나게… 🤦🏻♀️

이런 불편함을 겪고
반드시 이번에는 React-Hook-Form을 사용해서 폼을 구현하고 싶다라는 생각을 갖게 되었고 다행히 팀원도 같은 생각을 갖고 있어서 사용하기로 결정했다.
🧩 React-Hook-Form
코드를 바로 설명하기보다는 먼저 React Hook Form이 무엇인지 그리고 어떻게 사용하는지 간단히 소개한 뒤 실제 구현 코드를 보여주는 것이 더 이해하기 쉬울 것 같아 이 순서로 작성하겠다.!
React Hook Form은 React에서 폼을 간편하고 효율적으로 다룰 수 있게 도와주는 라이브러리이다.
React의 기본 폼 처리 방식은 대부분 useState를 활용해 상태를 관리하는데 이 방식은 입력 필드가 많아질수록 코드가 길어지고 매 입력마다 컴포넌트가 리렌더링되어 성능 문제가 발생할 수 있다.
하지만 React Hook Form은 "비제어 컴포넌트(uncontrolled components)" 기반으로 동작해서 최소한의 리렌더링으로 성능을 최적화 해주는 장점이 있다. 또한 유효성 검사와 에러 처리 그리고 서브밋 관리까지 아주 간단하게 할 수 있다는 장점이 있다👍🏻
🛠️ 주요 특징
입력 필드를
register함수 하나로 연결useForm()으로 폼 상태, 에러, 유효성 검사 관리선언형 유효성 검사 (
required)내부적으로
ref를 활용해 값과 상태를 추적하기 때문에 입력할 때마다 리렌더링되지 않는다.
// useState로 관리할 경우
<input value={value} onChange={(e) => setValue(e.target.value)} />
// react-hook-form의 register
<input {...register("name")} />
💡 사용 방법
설치방법
//npm
npm install react-hook-form
//yarn
yarn add react-hook-form
기본 사용법
- 가장 기본적인 형태는
useForm과register를 사용하는 방식이다.
const { register, handleSubmit, formState: { errors } } = useForm();
<input {...register("name", { required: "이름은 필수입니다" })} />
register를 통해 인풋에 바로 연결하면 되는데 이게 정말 간단하다. 유효성 검사도 객체 형태로 같이 전달하면 된다.
그런데 onChange 안에 로직이 필요하다면 register만으로는 부족하다.
그래서 등장한 것이 useController ✨
import { Controller } from 'react-hook-form'
const { control } = useForm();
const { field } = useController({
name: "username",
control,
rules: { required: "유저 이름은 필수입니다" }
});
<input
{...field}
onChange={(e) => {
console.log("입력된 값:", e.target.value);
field.onChange(e); // 기존 연결 유지
}}
/>
여기서 field.onChange를 꼭 호출해줘야 React Hook Form과의 연결이 유지된다.
이 외에도 React-Hook-Form 특징이 있는데
watch()로 실시간 값 추적 가능하다특정 필드의 값을 실시간으로 감지할 수 있다.
ex) 비밀번호 확인, 조건부 렌더링, 디버깅 등
// 'name' 필드 값이 바뀔 때마다 최신 값 반환
const name = watch("name");
useWatch– 특정 필드값 실시간으로 감시watch()는 전체 rerender를 유발하지만,useWatch()는 선택된 필드만 감시하며 리렌더링도 최소화된다.
const name = useWatch({ control, name: "name" });
.
reset,setValue,trigger등의 유틸 함수(커스텀 버튼이나 외부 이벤트로 폼 상태를 조작할 때 꼭 필요하다)reset()– 폼 초기화setValue()– 특정 필드 값 수동 설정trigger()– 특정 필드 또는 전체 폼의 유효성 검사 실행
ControllervsuseController차이 : 기능은 같지만 사용 방식이 다르다Controller: 컴포넌트 형태- JSX 안에서 사용
useController: 훅 형태- 로직 안에서 사용
// Controller 방식
<Controller
name="email"
control={control}
render={({ field }) => <input {...field} />}
/>
// useController 방식
const { field } = useController({ name: "email", control });
<input {...field} />
✅ 직접 사용해보자
나는 이번에 우리 프로젝트에서 메인 기능인 구인 & 구직 & 자유게시판 글 등록 폼 구현을 맡았다.

입력 해야 하는 내용들이 적지 않다.
(나는 현재 contents 부분에 editor를 사용하고 있다)
자유게시판
- useForm 초기 세팅
const WriteCommunityPostForm = () => {
const {
register,
handleSubmit,
reset,
setValue,
control,
formState: { errors },
} = useForm({
defaultValues: {
nickname: '',
title: '',
text: EditorState.createEmpty(),
},
})
register: 일반 input을 RHF에 등록할 때 사용handleSubmit: 유효성 검사 후 폼 제출을 처리reset: 폼 초기화setValue: 특정 필드 값을 프로그래밍적으로 설정(우리는 자유게시판에서 닉네임 값을 수정할 수 있어서 넣어줌)control:Controller를 사용할 때 필요errors: 각 필드의 유효성 검사 에러 정보
- onSubmit: 글 작성 시 처리 로직
const onSubmit = async (data) => {
const rawHtml = draftToHtml(convertToRaw(data.text.getCurrentContent()))
const htmlText = DOMPurify.sanitize(rawHtml)
const postData = {
nickname: data.nickname,
title: data.title,
text: htmlText,
}
}
DOMPurify.sanitize: HTML 내 악성 스크립트 제거convertToRaw→draftToHtml: HTML로 변환
폼 구조 (렌더링 부분)
1) 닉네임 입력
<JobInputBox {...register('nickname', { required: true })} placeholder='닉네임을 입력하세요' /> {errors.nickname && <span>닉네임은 필수입니다</span>}
register로 닉네임 input을 등록required: true유효성 검사만약 required: false이면 유효성 검사를 안 한다!
react-hook-form에서useForm()훅을 사용할 때formState.errors안에 유효성 검사를 통과하지 못한 필드의 정보가 들어간다.errors.nickname은nickname입력 필드에 에러가 존재할 경우에만 값이 존재
만약에 입력 폼을 다 채우지 않고 등록을 눌러주면 이런식으로 밑에 에러 text가 나오는 것을 확인할 수 있다.
2) 에디터 (글 내용)
<Controller name='text' control={control} rules={{ required: '내용은 필수입니다', validate: (value) => { const content = value.getCurrentContent() return content.hasText() || '내용은 필수입니다' }, }} render={({ field }) => ( <WriteEditor editorState={field.value} onEditorStateChange={field.onChange} placeholder='내용을 입력하세요' /> )} />커스텀 컴포넌트는
register로 직접 제어 불가 →Controller사용했다register는 일반적인<input>,<textarea>,<select>같은 기본 HTML 요소에만 직접 사용할 수 있다.나는 현재 editor를 사용하고 있어서 register로 직접 제어가 불가능한 상태이다
🔗 연결해주는 부분
render={({ field }) => (
<WriteEditor
editorState={field.value} // 현재 에디터 상태를 넘겨줌
onEditorStateChange={field.onChange} // 에디터 상태가 바뀔 때 react-hook-form에게 알려줌
placeholder='내용을 입력하세요'
/>
)}
나는 submit-button이 다른 파일에 있어서 form id로 연결해주었다.
//WriteCommunityPostForm.jsx
<form id='community-post-form' onSubmit={handleSubmit(onSubmit)}>
//WriteCommunityPost.jsx
<PinkButton label='등록' type='submit' form='community-post-form' />

완성 !! 👍🏻
🌱 배운 점
React Hook Form은 기존의 form 관리 방식을 훨씬 간단하고 효율적으로 만들어준다는 걸 체감했다.
특히register로 input에 바로 연결하고Controller를 통해 커스텀 컴포넌트와도 쉽게 연동할 수 있어서 편리했다.Controller를 사용하면 Draft.js 같은 복잡한 에디터 상태도 리액트 훅폼과 잘 통합할 수 있다. 이를 통해 에디터의 값도 쉽게 관리하고 유효성 검사도 유연하게 구현할 수 있었다.유효성 검사에서
required: true만으로 필수 입력 체크를 간편하게 처리할 수 있어서 굉장히 편리하다는 점을 배웠다.
✅ 느낀 점
React Hook Form을 처음 접했을 때는
Controller같은 개념이 조금 어렵게 느껴졌다. 하지만 실제로 구현하면서 점차 구조가 명확해지고 관리가 훨씬 편해져서 매우 만족스러웠다.특히 커스텀 에디터와 같은 복잡한 입력 컴포넌트와 연동할 때 별도의 상태 관리 코드를 줄일 수 있어 개발 효율성이 크게 올랐다.
form 상태와 유효성 관리를 직접 구현할 때 생기는 실수를 줄일 수 있다는 점도 마음에 들었다. 덕분에 코드가 더 깔끔해지고 유지보수가 쉬워졌다.
앞으로 React 프로젝트에서 form이 필요할 때마다 React Hook Form을 적극적으로 활용하고 더 고급 기능들도 공부하고 싶다는 동기부여가 되었다(아직은 기본만 사용할 줄 아는 수준…인 느낌..?!)
![[React] 클라이언트에서 이미지를 압축해보자](https://cdn.hashnode.com/res/hashnode/image/upload/v1759903597964/227ea5dc-8ff1-4999-9869-1bceb71b210a.png)
![[책 추천] 프론트엔드 개발자라면 반드시 알아야 할 '웹 접근성' 이야기](https://cdn.hashnode.com/res/hashnode/image/upload/v1753777028409/0438e77e-a609-48d8-989a-68191214a5b1.png)
![[Next.js] 나만의 학습 블로그 만들기#3-다국어 지원 (Feat.next-i18next)](https://cdn.hashnode.com/res/hashnode/image/upload/v1752472189546/a25d48d1-715a-4c90-a530-7dded2981fe7.png)
![[Next.js] 나만의 학습 블로그 만들기#4 - 댓글 기능(Feat. Giscus)](https://cdn.hashnode.com/res/hashnode/image/upload/v1752477512377/e1e868c0-6968-49f0-8fc5-91083b01ef79.png)
![[React] React + localStorage로 하루에 한 번만 보이는 팝업 만들기](https://cdn.hashnode.com/res/hashnode/image/upload/v1751525304623/4d143f95-eeac-42fb-9a95-b21d12d2e476.png)