Docs
GUIDES & CONCEPTS
Paginated Queries

Paginated / Lagged Queries

페이징된 데이터를 렌더링하는 것은 매우 일반적인 UI 패턴이며, TanStack Query에서는 쿼리 키에 페이지 정보를 포함시킴으로써 쉽게 처리할 수 있습니다:

const result = useQuery({
  queryKey: ["projects", page],
  queryFn: fetchProjects,
});

하지만 이 간단한 예제를 실행해 보면, 다음과 같은 이상한 현상을 경험할 수 있습니다:

UI가 successpending 상태를 오가는 이유는 각 페이지가 완전히 새로운 쿼리로 처리되기 때문입니다.

이 경험은 최적이 아니며, 현재 많은 도구들이 이러한 방식으로 작동하는 것이 사실입니다. 그러나 TanStack Query는 다릅니다! TanStack Query에는 placeholderData라는 멋진 기능이 있어서 이 문제를 해결할 수 있습니다.

Better Paginated Queries with placeholderData

다음 예제에서는 페이지 인덱스(또는 커서)를 증가시키는 상황을 고려해 보겠습니다. useQuery를 사용하면 기술적으로 잘 작동하긴 하지만, UI가 페이지나 커서가 바뀔 때마다 다른 쿼리가 생성되고 파괴되어 successpending 상태를 오가는 문제가 발생합니다. placeholderData(previousData) => previousData 또는 TanStack Query에서 제공하는 keepPreviousData 함수를 설정하면 다음과 같은 이점이 있습니다:

  • 이전 성공적인 데이터가 새로운 데이터가 요청되는 동안 사용 가능하므로 쿼리 키가 변경되더라도 데이터가 유지됩니다.
  • 새로운 데이터가 도착하면 이전 data가 원활하게 교체되어 새로운 데이터를 보여줍니다.
  • 현재 쿼리가 제공하는 데이터를 알기 위한 isPlaceholderData가 제공됩니다.
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import React from "react";
 
function Todos() {
  const [page, setPage] = React.useState(0);
 
  const fetchProjects = (page = 0) =>
    fetch("/api/projects?page=" + page).then((res) => res.json());
 
  const { isPending, isError, error, data, isFetching, isPlaceholderData } =
    useQuery({
      queryKey: ["projects", page],
      queryFn: () => fetchProjects(page),
      placeholderData: keepPreviousData,
    });
 
  return (
    <div>
      {isPending ? (
        <div>Loading...</div>
      ) : isError ? (
        <div>Error: {error.message}</div>
      ) : (
        <div>
          {data.projects.map((project) => (
            <p key={project.id}>{project.name}</p>
          ))}
        </div>
      )}
      <span>Current Page: {page + 1}</span>
      <button
        onClick={() => setPage((old) => Math.max(old - 1, 0))}
        disabled={page === 0}
      >
        Previous Page
      </button>
      <button
        onClick={() => {
          if (!isPlaceholderData && data.hasMore) {
            setPage((old) => old + 1);
          }
        }}
        // 다음 페이지 버튼을 비활성화하여 다음 페이지가 있는지 확인
        disabled={isPlaceholderData || !data?.hasMore}
      >
        Next Page
      </button>
      {isFetching ? <span> Loading...</span> : null}
    </div>
  );
}

Lagging Infinite Query results with placeholderData

다소 드물지만, placeholderData 옵션은 useInfiniteQuery 훅과도 완벽하게 작동하여 사용자가 무한 쿼리 키가 시간에 따라 변경되는 동안 캐시된 데이터를 계속 볼 수 있게 해줍니다.