공식문서 : https://tanstack.com/query/v4/docs/guides/paginated-queries
paginated 데이터를 렌더링하는것은 매우 일반적인 UI 패턴이며, React Query에서는 query key에 페이지 정보를 포함함로써 "그냥 작동"한다.
const result = useQuery(['projects', page], fetchProjects)
하지만, 이 간단한 예를 실행하면 다음과 같은 이상한 점을 발견할 수 있다. :
각각의 새 페이지가 완전히 새로운 쿼리로 취급되기 때문에 UI가 success
및 loading
상태로 이동한다.
이러한 경험은 최적이 아니며 안타깝게도 오늘날 많은 툴이 작동을 고집하고 있다. 하지만 React Query는 아니다! 짐작했겠지만, React Query에는 keepPreviousData
라는 놀라운 기능이 포함되어 있어 이러한 문제를 해결할 수 있다.
keepPreviousData
쿼리에 대한 pageIndex(또는 커서)를 이상적으로 증가시키고자하는 다음 예를 생각해보라. 만약 우리가 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(['projects', page], () => 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
일반적이진 않지만, keepPreviousData
옵션은 useInfiniteQuery
hook에서도 완벽하게 작동한다. 따라서 무한 쿼리 키가 변경되는 동안 유저가 계속해서 캐시된 데이터를 볼 수 있도록 원활하게 할 수 있다.