Skip to main content

Command Palette

Search for a command to run...

[React] React-Hook-Form 으로 폼(Form)을 구현하자 ‼️

Updated
6 min readView as Markdown
[React] React-Hook-Form 으로 폼(Form)을 구현하자 ‼️

왜 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

기본 사용법

  • 가장 기본적인 형태는 useFormregister를 사용하는 방식이다.
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 특징이 있는데

  1. watch()로 실시간 값 추적 가능하다

    • 특정 필드의 값을 실시간으로 감지할 수 있다.

    • ex) 비밀번호 확인, 조건부 렌더링, 디버깅 등

// 'name' 필드 값이 바뀔 때마다 최신 값 반환 
const name = watch("name");
  1. useWatch – 특정 필드값 실시간으로 감시

    • watch()는 전체 rerender를 유발하지만, useWatch()선택된 필드만 감시하며 리렌더링도 최소화된다.
const name = useWatch({ control, name: "name" });
  1. .reset, setValue, trigger 등의 유틸 함수(커스텀 버튼이나 외부 이벤트로 폼 상태를 조작할 때 꼭 필요하다)

    • reset() – 폼 초기화

    • setValue() – 특정 필드 값 수동 설정

    • trigger() – 특정 필드 또는 전체 폼의 유효성 검사 실행

  2. Controller vs useController 차이 : 기능은 같지만 사용 방식이 다르다

    • Controller: 컴포넌트 형태

      • JSX 안에서 사용
    • useController: 훅 형태

      • 로직 안에서 사용
// Controller 방식
<Controller
  name="email"
  control={control}
  render={({ field }) => <input {...field} />}
/>

// useController 방식
const { field } = useController({ name: "email", control });
<input {...field} />

✅ 직접 사용해보자

나는 이번에 우리 프로젝트에서 메인 기능인 구인 & 구직 & 자유게시판 글 등록 폼 구현을 맡았다.

입력 해야 하는 내용들이 적지 않다.

(나는 현재 contents 부분에 editor를 사용하고 있다)

자유게시판

  1. 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: 각 필드의 유효성 검사 에러 정보

  1. 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 내 악성 스크립트 제거

  • convertToRawdraftToHtml: HTML로 변환

  1. 폼 구조 (렌더링 부분)

    1) 닉네임 입력

     <JobInputBox
       {...register('nickname', { required: true })}
       placeholder='닉네임을 입력하세요'
     />
     {errors.nickname && <span>닉네임은 필수입니다</span>}
    
  • register로 닉네임 input을 등록

  • required: true 유효성 검사

    만약 required: false이면 유효성 검사를 안 한다!

  • react-hook-form에서 useForm() 훅을 사용할 때 formState.errors 안에 유효성 검사를 통과하지 못한 필드의 정보가 들어간다.

  • errors.nicknamenickname 입력 필드에 에러가 존재할 경우에만 값이 존재

    만약에 입력 폼을 다 채우지 않고 등록을 눌러주면 이런식으로 밑에 에러 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을 적극적으로 활용하고 더 고급 기능들도 공부하고 싶다는 동기부여가 되었다(아직은 기본만 사용할 줄 아는 수준…인 느낌..?!)

More from this blog

[React] 클라이언트에서 이미지를 압축해보자

우리는 서버 비용을 직접 부담하고 있기 때문에 항상 최적화와 비용 절감 방법을 고민하게 된다. 현재 자유게시판이 활성화가 많이 되지는 않았지만 사용자들이 업로드하는 이미지가 평균 5MB 이상일 경우를 대비해서 클라이언트에서 이미지 압축을 진행했다. 웹에서는 크게 두 가지 방법이 있었는데 Canvas API를 직접 사용하는 방법 browser-image-compression 라이브러리를 활용하는 방법 이 있다. 여러 장점을 고려해 brow...

Oct 8, 20252 min read
[React] 클라이언트에서 이미지를 압축해보자

[책 추천] 프론트엔드 개발자라면 반드시 알아야 할 '웹 접근성' 이야기

내가 생각하는 프론트엔드 개발자는 단순히 보이는 화면만 구현하는 것을 넘어서 누구나 접근할 수 있는 웹을 만드는 데 중요한 역할을 해야 한다고 생각한다. 실무에 바로 적용하는 웹 접근성 가이드북 이번에 이 책을 받고 프엔들이 읽기에 너무 좋다고 생각해서 블로그에 추천 글까지 적게 되었다 ! 목차 Chatper1. 쉽게 이해하는 접근성 Chapter2. 웹 접근성의 기초 Chapter3. HTML 태그, 의미 있게 사용하기 Chpater4...

Jul 27, 20255 min read
[책 추천] 프론트엔드 개발자라면 반드시 알아야 할 '웹 접근성' 이야기

[Next.js] 나만의 학습 블로그 만들기#3-다국어 지원 (Feat.next-i18next)

오늘은 다국어 지원 가능한 기능 구현 과정을 적어보려고 한다. 원래는 댓글기능보다 먼저 구현하려고 했는데 ,, 갑자기 댓글기능 알아보다가 재밌어서 먼저 끝내버렸다,,,🙄 시작하기 전에 정말 어려웠다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 어려움이 많아서 기능 적용하는 데 시간이 꽤 걸렸다 ,,, 다국어 지원 기능 나는 영어랑 일본어를 추가해줬다 ! (해외취업도 관심 있기 때문에 나중에 이력서 낼 때 도움이 되지 않을까? ..🤭) 다국어 구현 방법 구현 방...

Jul 15, 20254 min read
[Next.js] 나만의 학습 블로그 만들기#3-다국어 지원 (Feat.next-i18next)

[Next.js] 나만의 학습 블로그 만들기#4 - 댓글 기능(Feat. Giscus)

댓글기능이 있으면 무조건 재밌을 거 같아서 넣어보려고 한다🤭 아무래도 개발에 관련된 학습 블로그라서 개발자들이 많을 것으로 예상했다 ! 그래서 깃허브 이슈 기반 댓글 시스템을 기반으로 기능을 구현해보려고 한다. 👉🏻 도움이 많이 된 블로그 ! 방법 Giscus | (https://giscus.app) utterances | (https://utteranc.es) 알아봤을 때는 이렇게 두 개가 유명하다고 한다. 그럼 각각의 특징과 장...

Jul 14, 20253 min read
[Next.js] 나만의 학습 블로그 만들기#4 - 댓글 기능(Feat. Giscus)

[React] React + localStorage로 하루에 한 번만 보이는 팝업 만들기

이번 프로젝트에 설문조사 배너를 제작했는데… 흠… 뭔가 별로라는 의견이 많았다! (팀원들 의견) 🙄 디자인이 문제일까? 해서 래퍼런스를 많이 찾아봤는데 … ! 일반 웹사이트는 설문조사 배너 자체를 안 만들어요,,,🤭 충격 그 자체 ( 난 아직 멀었음 ) 그리고 알아보니깐.. 설문조사 배너는 사용자 경험을 해치기 쉬워 잘 쓰이지 않는다고 한다! 그래서 나는 모달 팝업 방식으로 전환하기로 결정했고 실무에서 자주 사용하는 패턴으로 구현했다. 방...

Jul 9, 20253 min read
[React] React + localStorage로 하루에 한 번만 보이는 팝업 만들기
S

subin-dev-blog

27 posts

끊임없이 배우기 위해 노력합니다. 🌱