React Hooks 도입 후 커스텀 훅으로 API 호출 로직 분리하기

배경

팀에서 React Hooks 도입을 결정한 후 기존 클래스 컴포넌트를 함수형으로 전환하는 작업을 진행 중이다. 여러 컴포넌트에서 API 호출 시 loading, error, data 상태를 각각 관리하는 코드가 반복되는 것을 발견했다.

기존 코드의 문제

각 컴포넌트마다 비슷한 패턴이 반복되었다.

function UserList() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [users, setUsers] = useState([]);

  useEffect(() => {
    setLoading(true);
    fetch('/api/users')
      .then(res => res.json())
      .then(data => setUsers(data))
      .catch(err => setError(err))
      .finally(() => setLoading(false));
  }, []);

  // ...
}

커스텀 훅으로 추상화

useFetch라는 커스텀 훅을 만들어 로직을 분리했다.

function useFetch(url) {
  const [state, setState] = useState({
    loading: true,
    error: null,
    data: null
  });

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

    fetch(url)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) {
          setState({ loading: false, error: null, data });
        }
      })
      .catch(error => {
        if (!cancelled) {
          setState({ loading: false, error, data: null });
        }
      });

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

  return state;
}

사용 예시

컴포넌트 코드가 훨씬 간결해졌다.

function UserList() {
  const { loading, error, data: users } = useFetch('/api/users');

  if (loading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;
  return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}

개선 효과

  • 보일러플레이트 코드 감소
  • 상태 관리 로직 일관성 확보
  • cleanup 로직(cancelled 플래그)을 한 곳에서 관리
  • 테스트 용이성 향상

다음에는 POST, PUT 등 다른 HTTP 메서드도 지원하도록 확장할 예정이다.

React Hooks 도입 후 커스텀 훅으로 API 호출 로직 분리하기