React 17에서 Custom Hook으로 폼 상태 관리 개선하기

문제 상황

회원가입, 프로필 수정, 게시글 작성 등 여러 페이지에서 폼을 다루는 코드가 반복되고 있었다. 각 컴포넌트마다 useState로 필드를 관리하고, onChange 핸들러를 작성하고, validation 로직을 구현하는 패턴이 계속 중복됐다.

useForm 훅 구현

공통 로직을 추출해 Custom Hook으로 만들었다.

import { useState, useCallback } from 'react';

function useForm(initialValues, validate) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleChange = useCallback((e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
    
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: null }));
    }
  }, [errors]);

  const handleSubmit = useCallback(async (onSubmit) => {
    const validationErrors = validate(values);
    
    if (Object.keys(validationErrors).length > 0) {
      setErrors(validationErrors);
      return;
    }

    setIsSubmitting(true);
    try {
      await onSubmit(values);
    } catch (error) {
      setErrors({ submit: error.message });
    } finally {
      setIsSubmitting(false);
    }
  }, [values, validate]);

  return { values, errors, isSubmitting, handleChange, handleSubmit };
}

export default useForm;

사용 예시

로그인 폼에 적용한 코드다.

function LoginForm() {
  const validate = (values) => {
    const errors = {};
    if (!values.email) errors.email = '이메일을 입력하세요';
    if (!values.password) errors.password = '비밀번호를 입력하세요';
    return errors;
  };

  const { values, errors, isSubmitting, handleChange, handleSubmit } = useForm(
    { email: '', password: '' },
    validate
  );

  const onSubmit = async (data) => {
    await api.login(data);
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      handleSubmit(onSubmit);
    }}>
      <input name="email" value={values.email} onChange={handleChange} />
      {errors.email && <span>{errors.email}</span>}
      
      <input name="password" type="password" value={values.password} onChange={handleChange} />
      {errors.password && <span>{errors.password}</span>}
      
      <button disabled={isSubmitting}>로그인</button>
    </form>
  );
}

결과

반복 코드가 크게 줄었고, 폼 로직의 일관성도 높아졌다. react-hook-form 같은 라이브러리 도입도 고려했지만, 프로젝트 규모에서는 이 정도 추상화로 충분했다.

React 17에서 Custom Hook으로 폼 상태 관리 개선하기