Pandaman Blog

[React Query] Pagination과 Infinite Scroll 본문

Front end/React-Query

[React Query] Pagination과 Infinite Scroll

oyg0420 2022. 5. 14. 17:34

1. Pagination

https://codesandbox.io/s/pagination-yeje-zcs5yh

 

Pagination 예제 - CodeSandbox

Pagination 예제 by oyg0420 using axios, isomorphic-unfetch, next, react, react-dom, react-query

codesandbox.io

React Query를 사용하면서 Pagination 구현을 어떻게 할 수 있을까? 사실 매우 간단하다.

useQuery(["projects", page])

단지 query key에 page 정보를 포함시킨다. page에 대한 데이터가 변경할 때마다 적용된 API를 fetch 할 것이다.

1.1. keepPreviousData 옵션

keepPreviousData 옵션을 true로 설정한 경우, 새 데이터가 요청되는 동안 마지막으로 성공적으로 가져온 데이터를 화면에 유지시켜 준다.
사실 설명으로 이해가 잘 안 되었다면 keepPreviousData 옵션을 off 시키고 페이지가 변경될 때의 화면을 살펴보자.

  const { data, isLoading, isPreviousData } = useQuery(
    ["projects", page],
    () => fetchProjects(page),
    {
      staleTime: 5000,
      keepPreviousData: false,
      select: (data) => {
        return {
          projects: data.projects,
          hasMore: data.totalPages > page
        };
      }
    }
  );

 return (
    <div>
      <p>페이지네이션 예제</p>
      isPreviousData: {String(isPreviousData)}
      <div>
        {isLoading
          ? "Loading.."
          : data.projects.map((project) => (
              <p key={project.id}>{project.name}</p>
            ))}
      </div>

위와 같이 Loading 중인 경우는 "Loading.."이라는 문구를 노출시키며 데이터가 settle 된 경우 프로젝트 리스트를 노출시킨다. 페이지가 변경될 때마다. 캐시 된 데이터가 없다면 Loading 중인 경우 이전 데이터를 나타내는 리스트는 화면에서 없어지고 "Loading.." 문구가 노출된다. 다시 새 데이터를 가져오면 새 리스트를 노출시킨다. keepPreviousData를 true로 변경하면 이전 데이터가 존재한다면, 새 데이터를 불러오기 전까지 이전 데이터를 유지시켜 준다. 사용자 관점에서 화면이 일관성 있게 유지되고 데이터만 변경되는 것이 더 자연스럽지 않을까 하는 생각이 들었다.

1.2. prefetchQuery

예제를 자세히 보면 prefetchQuery를 사용하고 있다. 그렇다면 prefetchQuery는 무엇일까?
이미 예상했겠지만, 데이터를 미리 가져와 캐시 하는 역할을 한다. 그렇다면 언제, 왜 필요할까?

아래는 예제 일부 코드이다. useEffect hook의 dependency을 보니 page 가 변경될 때 ["projects", page + 1] 쿼리, 즉 다음 페이지의 쿼리를 프리 패칭 하는 것이다.

React.useEffect(() => {
    if (data?.hasMore) {
      queryClient.prefetchQuery(["projects", page + 1], () =>
        fetchProjects(page + 1)
      );
    }
  }, [data, page, queryClient]);

prefetchQuery을 사용하면 다음 페이지로 이동할 때 이미 다음 페이지의 데이터를 캐시 하고 있어, 기다리는 시간 없이 바로 화면에 렌더링 되겠구나!라는 생각이 든다. 아주 좋은 기법이다.

2. Infinite Scroll

https://codesandbox.io/s/infinite-scroll-yeje-ecsyot?file=/pages/index.js:596-614 

 

Infinite Scroll 예제 - CodeSandbox

Infinite Scroll 예제 by oyg0420 using axios, isomorphic-unfetch, next, react, react-dom, react-intersection-observer, react-query

codesandbox.io

Infinite Scroll 도 많이 사용하는 기능이다. 보통 Intersection Observer API를 통해서 타깃과 교차되는 시점에 다음 페이지를 fetching 한다. 예제도 역시 동일하다. 여기서 우리는 react-query의 useInfiniteQuery를 사용할 것이다.

  const {
    data,
    isFetchingNextPage,
    hasNextPage,
    fetchNextPage,
  } = useInfiniteQuery(
    "projects",
    async ({ pageParam = 0 }) => {
      const res = await axios.get("/api/projects?cursor=" + pageParam);
      return res.data;
    },
    {
      getNextPageParam: (lastPage) => lastPage.nextId ?? undefined
    }
  );

위에서 본 Pagination의 useQuery과 사용법이 약간 다르다. useInfiniteQuery의 첫 번째 인자는 동일하게 queryKey이고, 두 번째 인자는 queryFn이다. 이때 queryFn의 인자로 pageParam 이 전달된다. 여기서 초기값은 0으로 설정했다(pageParam = 0).

1.1. getNextPageParam

해당 쿼리가 새 데이터를 받으면 getNextPageParam는 마지막 페이지에 대한 데이터와 전체 페이지 리스트를 인자로 받는다(lastPage, allPages) => unknown | undefined). return 하는 값으로 다음 페이지를 결정한다. 즉 다음 fetch 때의 pageParam를 결정한다. 만약 undefined를 반환한다면 다음 페이지는 존재하지 않다고 인식하여, 더 이상 fetch 하지 않는다.
여기서 우리는 현재의 페이지가 마지막 페이지인지, 다음 페이지가 존재하는지 파악해야 한다. lastPage.last_page === allPages.length 인 경우는 allPages.length + 1 반환, 아니면 undefined를 반환하여 마지막 페이지임을 알려준다.
(참고로 lastPage.last_page의 last_page는 api로 내려온 속성이다. 전체 페이지가 얼마나 되는지는 api를 통해서 알아야 한다..)

1.2. fetchNextPage

다음 페이지의 데이터를 가져올 때 사용한다. 보통 타깃이 intersection 되었을 때 fetchNextPage를 호출하도록 설계한다.

1.3. hasNextPage

다음 페이지가 존재하는지 여부를 나타내며, getNextPageParam 에서 반환한 값이 undefined 이면 false 아니면 true 가 된다.

'Front end > React-Query' 카테고리의 다른 글

[React Query] Default Query Function  (0) 2022.05.15
[React Query] Query / Mutation 이해하기  (0) 2022.04.11
Comments