<aside> 📃

React Query 시리즈

React Query로 상태 관리와 성능 최적화하기 1: React Query 소개

React Query로 상태 관리와 성능 최적화하기 2: useQuery

React Query로 상태 관리와 성능 최적화하기 3: useInfiniteQuery

React Query로 상태 관리와 성능 최적화하기 5: useQuery, useMutation 차이

</aside>



♾️ useInfiniteQuery

useInfiniteQuery는 연속적으로 로드되는 데이터를 관리하기 위한 훅이다. 무한 스크롤, ‘Load More’ 버튼, 페이지네이션 등 데이터를 순차적으로 불러오는 모든 상황에서 사용할 수 있다. 기본적인 구조는 다음과 같다.

const {
	// 함수 반환 값
	data, // 로드된 모든 데이터를 포함하는 객체
	fetchNextPage, // 다음 데이터를 불러오는 함수
	hasNextPage, // 추가 데이터 존재 여부
	
	isFetching, // 데이터 요청 진행 중 여부
	isFetchingNextPage, // 추가 데이터 요청 진행 중 여부
	status, // 요청 상태 ('loading', 'error', 'success')
	error, // 에러 발생 시 에러 객체
} = useInfiniteQuery({
	// 함수 옵션
	queryKey, // 쿼리 식별자
	queryFn, // 데이터 요청 함수
	getNextPageParam, // 다음 데이터 요청을 위한 파라미터 생성 함수
	
	// 선택적 옵션
	initialPageParam, // 초기 요청 파라미터
	staleTime, // 데이터 신선도 유지 시간
	cacheTime, // 캐시 유지 시간
	retry, // 실패 시 재시도 횟수
})

이 훅의 가장 큰 특징은 data.pages 배열을 통해 모든 로드된 데이터를 순차적으로 관리한다는 점이다. 각 데이터 청크는 독립적으로 저장되고, 필요에 따라 병합해 사용할 수 있다. 또한 getNextPageParam 함수를 통해 다음 데이터 요청에 필요한 파라미터를 유연하게 정의할 수 있다.

도입 배경

우리가 지금까지 구현한 무한 스크롤과 같은 복잡한 데이터 로딩 패턴을 구현할 때 장점이 두드러진다. 아래 코드를 보자.

export function useInfiniteScroll<T>({ fetchData, initialPage = 1 }: UseInfiniteScrollOptions<T>) {
  const [items, setItems] = useState<T[]>([]);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);
  const [currentPage, setCurrentPage] = useState(initialPage);
  const observerTarget = useRef<HTMLDivElement>(null);

  const loadMore = useCallback(async () => {
    if (loading || !hasMore) return;

    setLoading(true);
    try {
      const response = await fetchData(currentPage);
      setItems((prev) => [...prev, ...response.data]);
      setHasMore(response.hasMore);
      setCurrentPage((prev) => prev + 1);
    } catch (error) {
      console.error("Failed to fetch posts:", error);
    } finally {
      setLoading(false);
    }
  }, [currentPage, loading, hasMore, fetchData]);
  
  // ...
}

이 구현에서는 여러 상태(items, loading, hasMore, currentPage)를 직접 관리하고, 데이터 로딩, 에러 처리, 상태 업데이트 등의 모든 로직을 직접 작성해야 한다. 반면 React Query를 사용하면 이러한 복잡한 상태 관리를 라이브러리가 대신 처리해준다.

function useInfinitePostsQuery() {
  return useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    getNextPageParam: (lastPage) => lastPage.nextPage,
    initialPageParam: 1
  });
}

React Query는 이런 문제점을 어느 정도 해결해준다. 상태 관리가 자동화되어 있어 개발자가 직접 관리해야 하는 상태의 수가 줄어들고 선언적인 방식으로 코드를 작성해 보기 편하다.