프로젝트/keynut

[ Next.js, React-Query ] useInfiniteQuery를 사용해 무한 스크롤 구현하기

코딩핑 2024. 7. 1. 17:02

 

 

중고 거래 사이트를 제작하면서 사용자 경험 개선을 위해 상품들에 infinite scroll을 적용하기로 결정하였다!



나는  스크롤의 마지막을 감지하기 위해 react-intersection-observer 라이브러리를 사용하고 데이터 요청에는 react-query를 사용했다. 

 

그 전에 infinite scroll을 적용하지 않고 prefetchQuery와 useQuery를 사용해 데이터를 불러오던 코드가 있었기 때문에 이를 prefetchInfiniteQuery와 useInfiniteQuery로 수정을 해주었다.

 

prefetchInfiniteQuery에는 데이터를 불러올 때  페이지 파라미터를 설정해주는 옵션이 필요하다. 0부터 시작하게 설정을 해주었다

 await queryClient.prefetchInfiniteQuery({ queryKey: ['products', ''], queryFn: getProducts, initialPageParam: 0 });

 

react-intersection-observer는 React에서 Intersection Observer API를 쉽게 사용할 수 있게 해주는 라이브러리로
관찰할 객체를 ref로 설정하여 해당하는 객체가 나타나면 이를 감지할 수 있다


다음과 같이 useInView 훅을 사용하여 설정해주었다.

threshold 옵션은 객체가 어느 정도 나타나면 상태를 바꿔줄지를 결정하고(0~1), delay 옵션은 감지 후 실행까지의 지연 시간을 설정한다.
inView 상태를 통해 ref로 설정한 객체를 만났는지 알 수 있다!

  const { ref, inView } = useInView({ threshold: 0, delay: 0 });

 

그래서 상품 목록 아래 div를 놔두고 해당 div를 ref로 가리켜 감시해주었다. inView의 값이 바뀌면 새로 데이터를 불러오도록 했다.

 

전체 코드는 다음과 같다.

  const useProducts = queryString => {
    return useInfiniteQuery({
      queryKey: ['products', queryString],
      queryFn: ({ pageParam }) => getProducts(queryString, pageParam),
      initialPageParam: 0, //[1,2,3,4,5] [6,7,8,9,10] [11,12,13,14,15] -> 데이터를 페이지별로 관리 , 이차원 배열
      getNextPageParam: (lastPage, allPages) => {
        if (lastPage.length === 0) return undefined;
        return lastPage[lastPage.length - 1]._id;
      },
    });
  };

  const { ref, inView } = useInView({ threshold: 0, delay: 0 });
  const { data, fetchNextPage, hasNextPage, isFetching, error, isLoading } = useProducts(initialQueryString());

  useEffect(() => {
    if (inView && !isFetching && hasNextPage) {
      fetchNextPage();
    }
  }, [inView, fetchNextPage]);

 

getNextPageParam은 다음 페이지의 매개변수를 결정한다.

lastPage는 가장 마지막에 가져온 페이지 데이터를 나타내고 이 데이터의 마지막 항목의 _id를 다음 페이지 매개변수로 보내줬다.  다음과 같이 해주면 위에 말한 inView 상태가 변경되면 fetchNextPage가 호출되어 마지막페이지의 마지막 항목 _id를 기준으로 새로운 페이지를 불러온다!

getProducts함수에서는 api에 매개변수로 받은 마지막 항목 _id를 querystring에 붙여서 보내주었고 GET함수에서는 이를 받아 해당 id다음 데이터 32개를 return해주도록 구현했다.

devtools에서 확인하면 처음에 shop페이지에 들어가면 다음과 같이 32개의 데이터를 가진 하나의 페이지가 있고


스크롤을 해주면 ref로 가리킨 컴포넌트를 만날 때 새로 페이지가 가져와진다

아래에서 보면 빨간 컴포넌트를 만나고 페이지가 새로 가져와져 스크롤이 올라간다!

 

이후에 devtools에서 확인하면 잘 불러와진걸 확인할 수 있다!


구현을 하면서 useEffect 의존성 배열에 isFetching도 넣어줬었는데 isFetching이 처음에는 false였다가 true로, 다시 false로 변경될 때 useEffect가 두 번 실행돼 문제가 발생했다. 처음에는 문제점을 몰라 debounce를 추가하는 등 억지로 요청을 무시하고 한번만 실행되도록 하려고 수정을 하고 있었는데 isFetching을 제거하니 문제가 해결됐다!
useEffect를 사용할 때 의존성 배열에 포함된 값들이 어떻게 동작하는지 확실히 이해하는 게 중요하다는 거를 다시 한번 느꼈다..!
결론적으로 무한 스크롤 적용 성공이다 :)