React Query를 사용한 무한 페이지네이션

SnowCat·2023년 8월 28일
0
post-thumbnail

useInfiniteQuery

  • 무한 페이지네이션을 구현하기 위해서는 데이터를 저장할 배열, 마지막으로 불러온 페이지 키를 저장할 객체, 데이터의 갱신여부를 확인해줄 함수 구현등을 직접 해야 한다.
  • 하지만 react query의 useInfiniteQuery 훅을 사용하면 코드 몇줄로 무한 페이지네이션을 쉽게 구현할 수 있다.
const {
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage,
  ...result
} = useInfiniteQuery({
  queryKey,
  queryFn: ({ pageParam = 1 }) => fetchPage(pageParam),
  getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
  getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
  ...options,
})
  • 주요한 입력값으로는 queryKey, queryFn, getNextPageParam이 있다.
    • queryKey: 쿼리를 구분해주는 키로 공식 문서에서는 배열 형식으로 작성해주는 것을 권장
    • queryFn: 쿼리를 수행할 함수로 pageParam을 입력으로 받아 무한 페이지네이션을 수행하는 함수
    • getNextPageParam: 마지막 페이지, 전체 페이지를 입력으로 받아 pageParam을 반환하는 함수
  • 주요한 출력값으로는 data, error, fetchNextPage, hasNextPage, isFetching, isFetchingNextPage, status가 있다.
    • data: queryFn을 통해 얻은 모든 페이지 데이터
    • error: 쿼리 시도시 발생한 error 객체
    • fetchNextPage: 무한 페이지네이션 함수
    • hasNextPage: 페이지네이션을 통해 가져올 다음 페이지가 존재하는 지여부
    • isFetching: 데이터 패칭여부
    • isFetchingNextPage: fetchNextPage 시행여부
    • status: 현재 데이터 페칭 상태를 나타내며, "fetching", "error", "success"가 존재함
  • 정의에서 알 수 있듯이 페이지네이션은 반대방향, 또는 양방향으로도 구현이 가능하다.

예시

  • 공식문서에서는 아래와 같은 예제를 제시하고 있다.
  • 프로젝트의 이름을 무한 스크롤 방식으로 출력하는 컴포넌트로, 프로젝트 결과값에 있는 nextCursor를 제공해 값을 받아오는 함수를 제작해, 이를 useInfiniteQuery hook에 사용하였다.
import { useInfiniteQuery } from '@tanstack/react-query'

function Projects() {
  const fetchProjects = async ({ pageParam = 0 }) => {
    const res = await fetch('/api/projects?cursor=' + pageParam)
    return res.json()
  }

  const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isFetchingNextPage,
    status,
  } = useInfiniteQuery({
    queryKey: ['projects'],
    queryFn: fetchProjects,
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  })

  return status === 'loading' ? (
    <p>Loading...</p>
  ) : status === 'error' ? (
    <p>Error: {error.message}</p>
  ) : (
    <>
      {data.pages.map((group, i) => (
        <React.Fragment key={i}>
          {group.data.map((project) => (
            <p key={project.id}>{project.name}</p>
          ))}
        </React.Fragment>
      ))}
      <div>
        <button
          onClick={() => fetchNextPage()}
          disabled={!hasNextPage || isFetchingNextPage}
        >
          {isFetchingNextPage
            ? 'Loading more...'
            : hasNextPage
            ? 'Load More'
            : 'Nothing more to load'}
        </button>
      </div>
      <div>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</div>
    </>
  )
}

데이터를 다시 가져와야 할 경우

  • 기본적으로 쿼리 데이터가 stale 상태, 즉 fresh하지 않은 상태가 되면, react query에서 데이터를 알아서 처음부터 가져오게 된다.
  • 만약 특정 페이지만을 다시 가져오고 싶거나, 수동으로 데이터를 가져오고 싶다면 refetch 함수를 사용하면 된다.
const { refetch } = useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: fetchProjects,
  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})

// 첫페이지만 다시로딩
refetch({ refetchPage: (page, index) => index === 0 })

페이지 정보를 수동으로 제공하고 싶은 경우

  • react query는 직전 fetch에서 가져온 getNextPageParam을 다음 쿼리의 pageParam으로 제공한다.
  • 수동으로 page param을 전달하고 싶은 경우 fetchNextPage 함수를 사용하면 된다.
function Projects() {
  const fetchProjects = ({ pageParam = 0 }) =>
    fetch('/api/projects?cursor=' + pageParam)

  const {
    status,
    data,
    isFetching,
    isFetchingNextPage,
    fetchNextPage,
    hasNextPage,
  } = useInfiniteQuery({
    queryKey: ['projects'],
    queryFn: fetchProjects,
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  })

  // 수동으로 page param 전달
  const skipToCursor50 = () => fetchNextPage({ pageParam: 50 })
}

페이지를 역순으로 표현하기

  • select 옵션을 통해 데이터 표시 순서를 조정할 수 있다.
useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: fetchProjects,
  select: (data) => ({
    pages: [...data.pages].reverse(),
    pageParams: [...data.pageParams].reverse(),
  }),
})

쿼리 데이터를 수동으로 조작하기

  • queryClient 내의 setQueryData 메서드를 사용하면 된다.
queryClient.setQueryData(['projects'], (data) => ({
  pages: data.pages.slice(1),
  pageParams: data.pageParams.slice(1),
}))

출처:
https://tanstack.com/query/v4/docs/react/guides/infinite-queries

profile
냐아아아아아아아아앙

0개의 댓글