지긋지긋한 폼과의 작별 (React Hook Form)

752025년 10월 29일4

웹 개발의 역사에서 ‘폼(Form)’은 언제나 개발자들을 가장 괴롭히는 존재 중 하나였다. 수많은 입력 필드, 복잡한 유효성 검사(validation) 규칙, 실시간 에러 메시지, 제출 상태 관리…

리액트의 세계에서도 이는 마찬가지였다.
useState를 사용하여 모든 입력 필드의 값을 제어하는 방식(제어 컴포넌트, controlled component)은 코드를 장황하게 만들었다. 더 심각한 문제는, 키보드를 한 번 입력할 때마다 setState가 호출되어 컴포넌트 전체가 리렌더링된다는 점이었다. 필드가 수십 개인 거대한 폼에서는, 이 잦은 리렌더링이 눈에 띄는 성능 저하를 유발했다.

이 지긋지긋한 문제를 해결하기 위해, 빌 러(Bill Luo)라는 개발자가 만든 React Hook Form이라는 라이브러리가 혜성처럼 등장했다.

이 라이브러리의 접근 방식은 기존과는 완전히 달랐다.
리액트가 모든 입력 값을 제어하는 대신, 폼의 상태 관리를 대부분 브라우저의 네이티브 폼 기능에 위임하는 ‘비제어 컴포넌트(uncontrolled component)’ 방식을 채택했다. 이를 통해 불필요한 리렌더링을 원천적으로 차단했다.

그리고 이 모든 것을 가능하게 한 중심에는, useForm이라는 강력한 커스텀 훅이 있었다.

한 개발팀이 수십 개의 필드를 가진 복잡한 사용자 등록 폼을 만들고 있었다. 그들은 useState를 남발하며 성능 문제와 복잡한 유효성 검사 로직에 시달리고 있었다.

그때, 한 개발자가 React Hook Form을 도입하자고 제안했다.
리팩토링된 코드는 경이로울 정도로 달라졌다.

import { useForm } from 'react-hook-form';

function RegistrationForm() {
  // useForm 훅을 호출한다.
  const { register, handleSubmit, formState: { errors } } = useForm();

  // 폼 제출 시 실행될 함수
  const onSubmit = (data) => {
    console.log(data); // 모든 폼 데이터가 객체로 깔끔하게 들어온다.
  };

  return (
    // handleSubmit으로 폼 제출을 감싼다.
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* register 함수를 사용하여 input을 등록한다. */}
      <input 
        {...register("username", { required: "사용자 이름은 필수입니다." })} 
      />
      {/* 에러 메시지를 쉽게 표시할 수 있다. */}
      {errors.username && <p>{errors.username.message}</p>}

      <input 
        type="email" 
        {...register("email", { required: true, pattern: /^\S+@\S+$/i })} 
      />
      {errors.email && <p>올바른 이메일 형식이 아닙니다.</p>}

      <input type="submit" />
    </form>
  );
}

더 이상 개별 필드를 위한 useState는 필요 없었다.
register 함수를 <input>에 연결해주기만 하면, 라이브러리가 알아서 해당 필드를 등록하고 그 값을 추적했다.

유효성 검사 로직은 register 함수의 두 번째 인자로 간단하게 선언할 수 있었다. required, minLength, pattern 등 강력한 규칙들이 내장되어 있었다.

가장 놀라운 점은, 사용자가 입력 필드에 타이핑을 할 때 컴포넌트는 전혀 리렌더링되지 않는다는 것이었다. 라이브러리는 내부적으로 useRef를 활용하여 DOM 요소에 직접 접근하고, 오직 폼이 ‘제출’되거나 유효성 검사에 ‘실패’하는 등 꼭 필요한 순간에만 상태를 업데이트하고 리렌더링을 유발했다.

useForm 훅은 폼 관리의 모든 골칫거리—상태 관리, 유효성 검사, 성능 최적화—를 하나의 우아한 API로 해결했다.

이것은 커스텀 훅이 얼마나 효율적인 개발 패턴을 만들어낼 수 있는지를 보여주는 또 다른 강력한 증거였다. 훅은 단순히 코드를 재사용하는 것을 넘어, 기존의 접근 방식(제어 컴포넌트) 자체에 의문을 제기하고, 더 나은 대안을 제시하는 혁신의 촉매제가 되고 있었다.

리액트 생태계의 개발자들은 이제 더 이상 폼을 두려워할 필요가 없게 되었다.
그들의 시선은 이제, 애플리케이션의 상태 관리라는, 더 크고 본질적인 문제로 향하고 있었다. Redux는 강력했지만, 모든 프로젝트에 사용하기에는 너무 무거웠다. useContext는 편리했지만, 불필요한 리렌더링 문제에서 자유롭지 못했다.

이 두 가지 방식의 장점만을 취한, 더 가볍고 유연한 상태 관리 솔루션은 없을까?
커뮤니티는 훅을 기반으로, 이 질문에 대한 새로운 답을 찾기 시작했다.