react-query v4 공식문서 : Infinite Queries

👾·2022년 8월 11일
0

react-query

목록 보기
14/27
post-thumbnail

공식문서 : https://tanstack.com/query/v4/docs/guides/infinite-queries

Infinite Queries

기존 데이터 집합에 더 많은 데이터를 추가로 로드하거나 "무한 스크롤"할 수 있는 렌더링 리스트도 매우 일반적인 UI 패턴이다. React Query는 이러한 유형의 리스트들을 쿼리하기 위한 useInfiniteQuery라는 매우 유용한 버전의 useQuery를 지원한다.

useInfiniteQuery 사용시 몇가지 차이점이 있다 :

  • 이제 data가 무한 쿼리 데이터를 포함하고 있는 객체이다.

  • data.pages fetch한 페이지들을 포함하는 배열

  • data.pageParams 페이지를 fetch하는데 사용되는 페이지 파라미터들을 포함하는 배열

  • 이제 fetchNextPagefetchPreviousPage 함수들을 사용할 수 있다.

  • getNextPageParamgetPreviousPageParam 옵션을 사용하여 로드할 데이터가 더 있는지와 fetch할 정보가 있는지 확인할 수 있다. 이 정보는 쿼리 함수에서 추가 파라미터로 제공된다(fetchNextPage 또는 fetchPreviousPage 함수를 호출할때 선택적으로 재정의할 수 있음)

  • 이제 hasNextPage boolean을 사용할 수 있으며 getNextPageParamundefined 외의 값을 반환하면 true.

  • 이제 hasPreviousPage boolean을 사용할 수 있으며 getPreviousPageParamundefined 이외의 값을 반환하면 true

  • isFetchingNextPageisFetchingPreviousPage boolean들을 사용하여 백그라운드 refresh 상태와 추가 loading 상태를 구분할 수 있다.

참고 : 쿼리에서 initialData 또는 select 같은 옵션을 사용할 때, 데이터를 재구성할때 여전히 data.pagesdata.pageParams 속성을 포함하는지 확인해라. 그렇지 않으면 반환되는 쿼리에서 변경사항을 덮어쓰게 된다!

Example

다음 projects 그룹을 fetch하는데 사용할 수 있는 커서와 함께 커서 인덱스를 기반으로 projects의 페이지 3개를 한번에 반환하는 API가 있다고 가정해보자.

fetch('/api/projects?cursor=0')
// { data: [...], nextCursor: 3}
fetch('/api/projects?cursor=3')
// { data: [...], nextCursor: 6}
fetch('/api/projects?cursor=6')
// { data: [...], nextCursor: 9}
fetch('/api/projects?cursor=9')
// { data: [...] }

이 정보로, 다음을 통해 "Load More" UI를 만들 수 있다 :

  • 기본적으로 첫번째 데이터 그룹을 요청할때까지 useInfiniteQuery 대기중
  • getNextPageParam에서 다음 쿼리에 대한 정보 반환
  • fetchNextPage 함수 호출

참고 : getNextPageParam 함수에서 반환된 pageParam 데이터를 재정의하지 않으려면 인수를 사용하여 fetchNextPage를 호출하지 않는것이 매우 중요하다. 예를들어, 이렇게 하지마라 : <button onClick={fetchNextPage} />. 이는 onClick 이벤트를 fetchNextPage 함수로 보내기 때문

import { useInfiniteQuery } from '@tanstack/react-query'

function Projects() {
  const fetchProjects = ({ pageParam = 0 }) =>
    fetch('/api/projects?cursor=' + pageParam)

  const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isFetchingNextPage,
    status,
  } = useInfiniteQuery(['projects'], 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.projects.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>
    </>
  )
}

What happens when an infinite query needs to be refetched?

infinite 쿼리가 stale하게 되고 refetch해야하는 경우, 각 그룹은 첫번째 그룹부터 시작하여 sequentially fetch된다. 이렇게 하면 기본 데이터가 변경되더라도 stale한 커서를 사용하지 않고 잠재적으로 중복을 가져오거나 레코드를 건너뛰지 않게 한다. queryCache에서 infinite 쿼리의 결과가 제거되면, pagination은 초기 그룹만 요청된 초기 state에서 재시작된다.

refetchPage

모든 페이지의 하위집합만 refetch 하고싶다면, refetchPage 함수를 useInfiniteQuery에서 반환된 refetch로 전달할 수 있다.

const { refetch } = useInfiniteQuery(['projects'], fetchProjects, {
  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})

// only refetch the first page
refetch({ refetchPage: (page, index) => index === 0 })

이 함수를 두번째 인자(queryFilters)의 일부로 queryClient.refetchQueries, queryClient.invalidateQueries 또는 queryClient.resetQueries에 전달할 수도 있다.

Signature
refetchPage: (page: TData, index: number, allPages: TData[]) => boolean
이 함수는 각 페이지에 대해 실행되며, 이 함수가 true를 반환하는 페이지만 refetch된다.

What if I need to pass custom information to my query function?

기본적으로 getNextPageParam에서 반환된 변수는 쿼리 함수에 제공되지만, 경우에 따라 이를 재정의할 수 있다. 다음과 같이 기본 변수를 재정의하는 fetchNextPage 함수에 커스텀 변수를 전달할 수 있다.

function Projects() {
  const fetchProjects = ({ pageParam = 0 }) =>
    fetch('/api/projects?cursor=' + pageParam)

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

  // Pass your own page param
  const skipToCursor50 = () => fetchNextPage({ pageParam: 50 })
}

What if I want to implement a bi-directional infinite list?

양방향 list는 getPreviousPageParam, fetchPreviousPage, hasPreviousPageisFetchingPreviousPage 속성 및 함수를 사용하여 구현할 수 있다.

useInfiniteQuery(['projects'], fetchProjects, {
  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
})

What if I want to show the pages in reversed order?

때로는 페이지를 역순으로 표시하고 싶을 수 있다. 이 경우 select 옵션을 사용할 수 있다 :

useInfiniteQuery(['projects'], fetchProjects, {
  select: data => ({
    pages: [...data.pages].reverse(),
    pageParams: [...data.pageParams].reverse(),
  }),
})

What if I want to manually update the infinite query?

첫 페이지를 수동으로 제거:

queryClient.setQueryData(['projects'], data => ({
  pages: data.pages.slice(1),
  pageParams: data.pageParams.slice(1),
}))

개별 페이지에서 수동으로 단일 값 제거 :

const newPagesArray = oldPagesArray?.pages.map((page) =>
  page.filter((val) => val.id !== updatedId)
) ?? []

queryClient.setQueryData(['projects'], data => ({
  pages: newPagesArray,
  pageParams: data.pageParams,
}))

pages와 pageParams의 데이터 구조를 동일하게 유지해야한다!

0개의 댓글