React Hooks 도입 후 Custom Hook으로 API 호출 로직 분리하기

문제 상황

팀 프로젝트에서 React 16.8로 업그레이드하고 Hooks를 본격적으로 사용하기 시작했다. 여러 컴포넌트에서 API 호출 코드가 반복되면서 로딩 상태와 에러 처리 로직이 중복되는 문제가 있었다.

기존 Class 컴포넌트에서는 HOC나 render props 패턴을 사용했지만, 코드가 복잡해지는 단점이 있었다.

해결: useFetch Custom Hook

API 호출 로직을 재사용 가능한 Custom Hook으로 분리했다.

import { useState, useEffect } from 'react';

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

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

    async function fetchData() {
      try {
        setLoading(true);
        const response = await fetch(url);
        const json = await response.json();
        
        if (!cancelled) {
          setData(json);
          setError(null);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }

    fetchData();

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

  return { data, loading, error };
}

사용 예시

컴포넌트에서는 간단하게 사용할 수 있다.

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

  if (loading) return <div>로딩중...</div>;
  if (error) return <div>에러: {error}</div>;

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

개선 포인트

  • cleanup 함수로 unmount 시 메모리 누수 방지
  • 로딩/에러 상태를 일관되게 처리
  • 컴포넌트 코드가 훨씬 간결해짐

향후에는 캐싱이나 재시도 로직도 추가할 예정이다. Hooks의 조합 가능성이 생각보다 강력하다는 걸 체감했다.

React Hooks 도입 후 Custom Hook으로 API 호출 로직 분리하기