무한 스크롤에서 마지막 페이지 처리하기

Theo14·2024년 12월 2일
0

Show me the Recipes

목록 보기
1/1
post-thumbnail

SHOW ME THE RECIPES 프로젝트에서useInfiniteQueryreact-intersection-observer를 활용한 무한 스크롤 구현했다.
useInfiniteQuery를 사용해 데이터를 페이징하여 가져오고, react-intersection-observer를 활용해 사용자가 스크롤할 때 다음 페이지를 자동으로 로드하는 무한 스크롤 기능을 구현하였다.실질적으로 페칭되는 getRecipes는 action처리 하였다.

//useInfiniteRecipes.ts
const useInfiniteRecipes = () => {
  return useInfiniteQuery({
    queryKey: ['recipes'],
    queryFn: ({ pageParam = 1 }) => getRecipes(pageParam),
    getNextPageParam: (lastPage, allPages) => {
      const nextPage = allPages.length + 1
      const totalFetched = allPages.length * lastPage.limit
      if (totalFetched < lastPage.total) {
        return nextPage
      }
      return undefined
    },
    initialPageParam: 1,
  })
}

그리고 RecipesList페이지에서 isAllDataLoaded를 구하기위해 다음과 같은 연산을 사용했다.

//RecipesList.tsx	
  const {
    data,
    isLoading,
    error,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuotes()
  const { ref, inView } = useInView()
  
  useEffect(() => {
    if (inView && hasNextPage && !isFetchingNextPage) {
      fetchNextPage()
    }
  }, [inView, fetchNextPage, hasNextPage, isFetchingNextPage])

const totalRecipes =
    data?.pages?.reduce((sum, { recipes }) => sum + recipes.length, 0) ?? 0
  const lastPage = data?.pages?.[data.pages.length - 1]
  const isAllDataLoaded = lastPage?.total === totalrecipes 
  
  // <--중간 생략-->
   <div ref={ref}>
        {isAllDataLoaded ? 'No more quotes to display.' : 'Loading more...'}
      </div>
  

하지만 이러한 접근 방식이 매번 RecipesList페이지에서 isAllDataLoaded를 구해야하는 불필요한 연산 로직이 반복되는 문제 발생시킨다는 것을 발견했다.
문제를 해결하기위해 tanstackQuery 공식문서 useInfiniteQuery 다시 살펴 보았다.
공식문서에 getNextPageParam 옵션이 존재했고 getNextPageParam함수의 특징은 이러하다.

getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => TPageParam | undefined | null

  • Required
  • When new data is received for this query, this function receives both the last page of the infinite list of data and the full array of all pages, as well as pageParam information.
  • It should return a single variable that will be passed as the last optional parameter to your query function.
  • Return undefined or null to indicate there is no next page available.
  • 이 쿼리에서 새로운 데이터가 수신되면, 이 함수는 무한 데이터 목록의 마지막 페이지와 전체 페이지 배열, 그리고 pageParam 정보를 인수로 받는다.
  • 쿼리 함수의 마지막 선택적 매개변수로 전달될 단일 변수를 반환해야 한다.
  • 다음 페이지가 없음을 나타내려면 undefined 또는 null을 반환한다.

getNextPageParam 옵션을 통해 다음 페이지가 가져올 수 있는지 여부를 알아 낼 수 있음으로, 다음 페이지가 있으면 true로 설정할수 있는 값이었다. 이 값을 통해 nextPage가 있으면 true, 없으면 undefined를 리턴한다는 특징을 이용해 isAllDataLoaded연산을 삭제하고 진행했다.

//useInfiniteRecipes.ts
const useInfiniteRecipes = () => {
  return useInfiniteQuery({
    queryKey: ['recipes'],
    queryFn: ({ pageParam }) => getRecipes(pageParam),
    getNextPageParam: (lastPage, _, lastPageParam) => {
      return lastPage.limit === 0 ? undefined : lastPageParam + 1;
    },
    initialPageParam: 1,
  });
};

그리고 마지막에 isAllDataLoaded를 지우고 isAllDataLoaded대신 isFetchingNextPagehasNextPage를 통해 마지막 데이터 인지 표시하였다.

//RecipesList.tsx
      <div ref={ref}>
          {isFetchingNextPage
            ? 'Loading more...'
            : hasNextPage
            ? 'Load More'
            : 'No more recipes to display.'}
        </div>

Reference

https://tanstack.com/query/latest/docs/framework/react/reference/useInfiniteQuery

0개의 댓글