[Tanstack Query v4] 무한 스크롤 구현하는 방법 알아보기

sangsang·2024년 7월 17일
0
post-thumbnail

Tanstack Query를 사용해서 무한 스크롤 구현하기

푸디로그에서 식당 리뷰를 보여주는 피드 목록을 무한 스크롤로 어떻게 구현하였는지 분석해보았다.

무한스크롤
Tanstack Query를 사용해서 데이터 페칭하고, react-infinite-scroller를 사용해서 페이지를 스크롤할 때 추가 데이터를 불러온다.

푸디로그에서 사용했던 버전

  • tanstack query 4.33.0버전
  • react-infinite-scroller ^1.2.6버전

useInfinite Query

훅에서 사용한 Options

다음 코드는 푸디로그 피드 목록을 불러오는 쿼리를 useFeedListQuery 커스텀 훅으로 래핑한 코드.

import { RestaurantCategory } from "@/src/types/restaurant";
import { APIFeedResponse } from "@@types/apiTypes";
import { getFeedList, getFeedListByUserId } from "@services/feed";
import { useInfiniteQuery } from "@tanstack/react-query";

interface useFeedListQueryProps {
  userId?: number;
  singleFeedId?: number;
  category?: RestaurantCategory;
}

const useFeedListQuery = ({ userId, singleFeedId, category }: useFeedListQueryProps) => {
  return useInfiniteQuery(
    ["feedList", userId, category],
    async ({ pageParam = 0 }) => {
      let response;
      if (userId) {
        response = await getFeedListByUserId(userId, pageParam);
      } else {
        response = await getFeedList(pageParam, category);
      }

      const apiResponse = response.data;
      return apiResponse;
    },
    {
      getNextPageParam: (lastPage: APIFeedResponse) => {
        const lastFeed = lastPage.response.content.at(-1);
        if (lastPage?.response?.content?.length <= 15) return undefined;
        return lastFeed?.feed.feedId;
      },
      enabled: !singleFeedId || !userId,
    }
  );
};
export default useFeedListQuery;
  • queryKey
    기본적으로 쿼리 키에 따라 쿼리 캐싱을 관리한다.
    쿼리 키에 종속 변수를 추가하면 쿼리가 독립적으로 캐시되고 변수가 변경될 때마다 쿼리가 자동으로 다시 페치된다.

  • Query Functions: (context: QueryFunctionContext) => Promise
    프로미스를 반환하는 함수. 데이터를 반환하거나 오류 반환한다.

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

    다음 페이지를 가져올 파라미터를 반환해서 계속 다음 페이지를 불러올 수 있도록 한다.

    위 예시 코드에선 lastPage 길이가 15를 초과하면 lastFeed의 FeedId를 반환하고, 길이 15이하면 불러올 다음 페이지가 없기 때문에 undefind 반환한다.

    반환되는 값에 따라 hasNextPage 값이 boolean으로 반환된다.

훅에서 사용한 Retruns

다음 코드에서 useFeedListQuery를 호출하여 데이터를 페칭하고, InfiniteScroll 컴포넌트를 사용해 무한 스크롤을 구현한다. data.pages를 순회하며 피드 데이터를 렌더링한다.

"use client";
import { Fragment, useEffect, useRef, useState } from "react";
import { getSingleFeed } from "@services/feed";
import InfiniteScroll from "react-infinite-scroller";
import Feed from "@components/Feed/Feed";
import { Content } from "@@types/feed";
import useFeedListQuery from "@hooks/queries/useFeedListQuery";

interface FeedsProps {
id?: number;
startingFeedId?: number;
singleFeedId?: number;
}

const Feeds = ({ id, startingFeedId, singleFeedId }: FeedsProps) => {
const [singleFeedData, setSingleFeedData] = useState<Content | null>(null);
const feedRef = useRef<{ [key: number]: HTMLDivElement | null }>({});

const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useFeedListQuery({ userId: id, singleFeedId });

return (
  <div className="flex flex-col pt-5 max-w-[640px] w-full mx-auto">

      <InfiniteScroll pageStart={0} loadMore={loadMore} hasMore={hasNextPage && !isFetchingNextPage}>
        {(data?.pages || []).map((page, index) => {
          console;
          if (!Array.isArray(page.response?.content)) {
            return null;
          }
          return (
            <Fragment key={index}>
              {page?.response.content.map((feedData: Content, index) => {
                const { feed } = feedData;
                const hasFeedId = feed?.feedId !== undefined;
                const Key = hasFeedId ? feed.feedId : index;

                return (
                  <div
                    key={Key}
                    ref={(el) => {
                      if (hasFeedId) {
                        feedRef.current[feed.feedId] = el;
                      }
                    }}
                  >
                    <Feed key={Key} feedData={feedData} userId={id} />
                  </div>
                );
              })}
            </Fragment>
          );
        })}
      </InfiniteScroll>
    )}
  </div>

};

export default Feeds;
  • data
    • data.pages: 모든 페이지를 포함하는 배열.
  • fetchNextPage: (options?: FetchNextPageOptions) => Promise
    다음 "페이지"의 결과를 가져올 수 있다
  • hasNextPage: boolean
    가져올 다음 페이지가 있는 경우 true. (getNextPageParam 반환값에 따라 boolean 반환)
  • isFetchingNextPage: boolean
    fetchNextPage 로 다음 페이지를 가져오는 동안에는 true.

InfiniteScroll

react-infinite-scroller 라이브러리의 InfiniteScroll 컴포넌트는 스크롤 이벤트를 감지하여 스크롤이 뷰포트 끝에 도달하면 loadMore 함수를 호출하여 추가 데이터를 로드하는 기능을 제공한다.

  <InfiniteScroll pageStart={0} loadMore={loadMore} hasMore={hasNextPage && !isFetchingNextPage}>
  • pageStart
    초기 페이지 번호를 설정.
  • loadMore
    추가 데이터를 로드할 때 호출되는 함수. 다음 데이터를 서버로부터 가져와서 현재 리스트에 추가한다.
  • hasMore
    추가로 로드할 데이터가 있는지 여부를 나타내는 boolean.
    true이면 loadMore가 호출되어 더 많은 데이터를 가져오고, false이면 더 이상 데이터를 가져오지 않는다.
    hasMore={hasNextPage && !isFetchingNextPage}
    hasMore가 false일 때, 추가 데이터 요청 중지
    isFetchingNextPage가 true일 때(현재 데이터가 로딩 중일 때), loadMore 함수를 호출하지 않도록 하여, 중복 요청을 방지한다.

참고

profile
개발이 너무 좋다. 정말 잘 하고 싶다.

0개의 댓글

관련 채용 정보