frontend/react

[React] React.js 실무 강좌 33. React Hook Form으로 폼 상태를 깔끔하게 관리하기

mirabo01 2025. 11. 11. 08:52

React로 폼을 만들다 보면 “입력값이 제대로 들어갔는지”, “에러 메시지를 언제 보여줄지”, “제출할 때 상태가 맞는지” 등
생각보다 챙겨야 할 게 정말 많습니다.
저도 초창기에는 useState로 모든 input 값을 관리했는데,
폼이 조금만 커져도 코드가 금세 복잡해지더군요.

그런데 React Hook Form을 쓰고 나서부터는 폼 관리가 한결 편해졌습니다.
특히 입력값 검증, 초기값 세팅, 제출 이벤트를 일관된 방식으로 다룰 수 있어서
실무에서 가장 자주 쓰는 훅 중 하나가 됐습니다.


예전엔 useState만으로 버텼던 시절

처음엔 단순히 이렇게 코드를 짰습니다.

const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleSubmit = (e) => {
  e.preventDefault();
  console.log({ email, password });
};

이 구조의 문제는 간단합니다.
필드가 늘어날수록 상태도 늘어나고,
검증 로직을 넣으면 if (!email.includes('@')) ... 같은 코드가 끝도 없이 길어집니다.

작은 폼에는 괜찮지만,
회원가입이나 설정 페이지처럼 필드가 많은 폼에서는
한눈에 봐도 관리하기가 버거워집니다.


React Hook Form을 처음 도입했을 때

React Hook Form을 쓰려면 일단 기본 훅부터 가져옵니다.

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

function SignupForm() {
  const { register, handleSubmit, formState: { errors } } = useForm();
  const onSubmit = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email', { required: '이메일을 입력해주세요.' })} />
      {errors.email && <p>{errors.email.message}</p>}

      <input {...register('password', { minLength: { value: 6, message: '6자 이상 입력하세요.' } })} />
      {errors.password && <p>{errors.password.message}</p>}

      <button type="submit">가입하기</button>
    </form>
  );
}

이 코드를 보고 처음엔 좀 낯설었습니다.
하지만 금방 이해됐어요.
register는 input과 React Hook Form의 내부 상태를 연결해주고,
handleSubmit은 제출 시 자동으로 검증을 실행합니다.

무엇보다 좋았던 점은 폼 상태를 useState로 따로 관리하지 않아도 된다는 것이었습니다.


이걸 몰라서 삽질했던 부분

React Hook Form은 내부적으로 ref를 사용하기 때문에
컨트롤드(Controlled) 컴포넌트와 살짝 다르게 작동합니다.
처음에는 input에 value를 직접 지정해두고 register를 함께 써서
“왜 값이 안 바뀌지?” 하고 며칠을 헤맨 적이 있습니다.

React Hook Form을 사용할 땐
기본적으로 input의 상태를 직접 제어하지 않아야 합니다.
대신 defaultValues 옵션으로 초기값을 넣어주는 게 맞습니다.

const { register, handleSubmit } = useForm({
  defaultValues: {
    email: 'example@gmail.com',
    password: '',
  },
});

이렇게 하면 input이 알아서 초기화되고,
직접 setState로 조작할 필요가 없어집니다.


실무에서 진짜 유용했던 기능

제가 가장 많이 쓰는 건 watch와 reset입니다.

watch는 실시간으로 입력값을 관찰할 때 정말 유용합니다.
예를 들어 비밀번호 확인 기능을 만들 때 이런 식으로 쓸 수 있죠.

const { register, handleSubmit, watch } = useForm();
const password = watch('password');
const passwordConfirm = watch('passwordConfirm');

두 값을 비교해서 에러 메시지를 띄우거나 버튼을 비활성화할 수 있습니다.

그리고 reset은 서버에서 받은 데이터를 폼에 다시 세팅할 때 유용합니다.
예를 들어 사용자 정보 수정 페이지를 만들 때,
useEffect로 데이터를 불러온 다음 이렇게 씁니다.

useEffect(() => {
  if (userData) {
    reset(userData);
  }
}, [userData]);

이 한 줄로 모든 필드가 자동으로 채워집니다.
이전에는 각각 setName, setEmail 등으로 수동으로 넣었는데
이제는 한 번에 관리할 수 있습니다.


써보면서 느낀 점

React Hook Form은 “작은 폼에서도 큰 효율을 주는 라이브러리”라고 생각합니다.
다른 폼 라이브러리보다 가볍고,
React의 훅 스타일에 정말 잘 어울립니다.

특히 다음 세 가지가 가장 마음에 들었습니다.

  • 불필요한 리렌더링이 거의 없다.
  • 에러 메시지를 설정하기 쉽다.
  • API 응답으로 폼을 쉽게 리셋할 수 있다.

폼 관리 때문에 useState 지옥에 빠져 있던 시절이 떠올라서,
지금은 웬만하면 무조건 React Hook Form으로 시작합니다.


다음 글에서는 React Hook Form과 zod를 함께 써서
유효성 검증을 더 견고하게 만드는 방법
을 다루겠습니다.
실무에서 서버 스키마와 클라이언트 검증 로직을 맞추는 데 꼭 필요한 부분입니다.