React Hooks 도입 후 Custom Hook 패턴 정리

배경

올해 초 React 16.8이 릴리즈되면서 Hooks가 정식 스펙이 되었다. 팀에서는 3개월 정도 관망하다가 7월부터 신규 컴포넌트에 Hooks를 적용하기 시작했다. 처음에는 useState, useEffect 정도만 사용했는데, 반복되는 패턴이 보이기 시작했고 Custom Hook으로 분리하는 게 효과적이었다.

useForm 구현

폼 상태 관리는 거의 모든 페이지에서 필요했다. 기존에는 각 컴포넌트마다 state를 선언하고 onChange 핸들러를 작성했는데, 이를 Hook으로 추상화했다.

function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
  };
  
  const resetForm = () => setValues(initialValues);
  
  return { values, handleChange, resetForm };
}

사용하는 쪽에서는 훨씬 간결해졌다.

function LoginForm() {
  const { values, handleChange } = useForm({ email: '', password: '' });
  
  return (
    <form>
      <input name="email" value={values.email} onChange={handleChange} />
      <input name="password" value={values.password} onChange={handleChange} />
    </form>
  );
}

useFetch 패턴

API 호출 로직도 반복이 많았다. loading, error, data 상태를 매번 선언하는 대신 Hook으로 만들었다.

function useFetch(url) {
  const [state, setState] = useState({ loading: true, data: null, error: null });
  
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => setState({ loading: false, data, error: null }))
      .catch(error => setState({ loading: false, data: null, error }));
  }, [url]);
  
  return state;
}

느낀 점

Custom Hook은 로직 재사용을 위한 새로운 방식이다. HOC나 render props보다 직관적이고 컴포넌트 트리도 깔끔하게 유지된다. 다만 useEffect 의존성 배열 관리가 까다로워서 ESLint의 exhaustive-deps 규칙을 켜두고 작업하는 게 필수다.

앞으로는 useLocalStorage, useDebounce 같은 유틸성 Hook도 만들어볼 예정이다.

React Hooks 도입 후 Custom Hook 패턴 정리