페이지네이션된 데이터를 렌더링하는 것은 매우 일반적인 UI 패턴이며, TanStack Query에서는 쿼리 키에 페이지 정보를 포함하면 "그냥 작동"합니다:
const result = useQuery({
queryKey: ['projects', page],
queryFn: fetchProjects
})
하지만 이 간단한 예제를 실행해 보면 이상한 점을 발견할 수 있습니다:
각각의 새 페이지가 완전히 새로운 쿼리처럼 취급되기 때문에 UI가 success
와 loading
상태를 오가는 것입니다.
이러한 환경은 최적의 환경이 아니며, 안타깝게도 오늘날 많은 툴들이 이러한 방식을 고집하고 있습니다. 하지만 TanStack Query는 그렇지 않습니다! 이미 짐작하셨겠지만, TanStack Query에는 이 문제를 해결할 수 있는 keepPreviousData
라는 멋진 기능이 함께 제공됩니다.
keepPreviousData
쿼리에 대해 페이지 인덱스(또는 커서)를 증가시키고자 하는 다음 예제를 생각해 보겠습니다. useQuery
를 사용하면 기술적으로는 여전히 정상적으로 작동하지만, 각 페이지 또는 커서마다 다른 쿼리가 생성 및 소멸됨에 따라 UI가 success
, loading
상태를 오가게 됩니다. keepPreviousData
를 true
로 설정하면 몇 가지 새로운 기능이 추가됩니다:
data
가 원활하게 교체되어 새 데이터가 표시됩니다.isPreviousData
를 사용할 수 있습니다.function Todos() {
const [page, setPage] = React.useState(0)
const fetchProjects = (page = 0) => fetch('/api/projects?page=' + page).then((res) => res.json())
const {
isLoading,
isError,
error,
data,
isFetching,
isPreviousData,
} = useQuery({
queryKey: ['projects', page],
queryFn: () => fetchProjects(page),
keepPreviousData : true
})
return (
<div>
{isLoading ? (
<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 (!isPreviousData && data.hasMore) {
setPage(old => old + 1)
}
}}
// Disable the Next Page button until we know a next page is available
disabled={isPreviousData || !data?.hasMore}
>
Next Page
</button>
{isFetching ? <span> Loading...</span> : null}{' '}
</div>
)
}
일반적이지는 않지만, keepPreviousData
옵션은 useInfiniteQuery
훅과도 완벽하게 작동하므로 무한 쿼리 키가 시간이 지남에 따라 변경되는 동안 사용자가 캐시된 데이터를 계속 볼 수 있도록 지원합니다.
Reference