React Hooks 도입 후 Custom Hook 패턴 정리

배경

올해 초 React 16.8이 정식 릴리즈되면서 Hooks를 프로덕션에 적용할 수 있게 되었다. 기존 클래스 컴포넌트와 HOC 패턴으로 작성된 코드베이스를 점진적으로 마이그레이션하면서, Custom Hook 패턴이 가장 효과적이었던 케이스를 정리한다.

useForm - 폼 상태 관리

가장 먼저 만든 건 폼 핸들링 훅이었다. 기존엔 각 컴포넌트마다 handleChange, handleSubmit을 반복 작성했다.

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

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

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsSubmitting(true);
    try {
      await onSubmit(values);
    } catch (err) {
      setErrors(err.response.data);
    } finally {
      setIsSubmitting(false);
    }
  };

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

사용하는 쪽은 이렇게 간결해졌다.

function LoginForm() {
  const { values, errors, isSubmitting, handleChange, handleSubmit } = useForm(
    { email: '', password: '' },
    (values) => api.login(values)
  );

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" value={values.email} onChange={handleChange} />
      {errors.email && <span>{errors.email}</span>}
      {/* ... */}
    </form>
  );
}

useFetch - API 호출 추상화

로딩/에러 상태를 매번 관리하는 것도 보일러플레이트였다.

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;

    fetch(url)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) {
          setData(data);
          setLoading(false);
        }
      })
      .catch(err => {
        if (!cancelled) {
          setError(err);
          setLoading(false);
        }
      });

    return () => { cancelled = true; };
  }, [url]);

  return { data, loading, error };
}

cleanup 함수로 컴포넌트 언마운트 시 상태 업데이트를 방지했다. 이전에 HOC로 구현했을 때보다 훨씬 직관적이다.

소감

Hooks는 로직 재사용 측면에서 HOC보다 명확한 이점이 있다. wrapper hell도 없고, props drilling도 줄었다. 다만 useEffect의 dependency array 관리는 여전히 실수하기 쉬워서 ESLint 플러그인이 필수다.

React Hooks 도입 후 Custom Hook 패턴 정리