React Query - useInfiniteQuery

October 29, 2022

useInfiniteQuery, useEffect를 이용한 무한 스크롤 구현

들어가기전

해당 글은 react-query의 useQuery와 react의 useEffect에 대한 선행이 필요하다.

이 글의 목표는

해당 페이지로 이동 시 상품 10개를 보여준 뒤 스크롤 최하단으로 이동했을 때

다음 상품 10개를 불러오고 이 과정을 반복하는 방법으로 무한 스크롤을 구현하고자 한다.



useInfiniteQuery 사용방법

먼저 React query의 useInfiniteQuery는 파라미터의 값만 변경하여 동일한 useQuery를 반복적으로 호출하기 위한 API이다.

useInfiniteQuery는 기본적으로 useQuery와 같은 형태로 사용된다. → 참조) useQuery

const response = useInfiniteQuery(queryKey, queryFn, options);

useInfiniteQuery엔 pageParam 개념이 등장한다. pageParam은 현재 위치를 확인할 수 있는 파라미터 값으로 queryFn의 파라미터 값에서 확인가능하다. (아래 코드 확인)


// infiniteQuery.tsx

export const useInfiniteProductListQuery = () => {
    // pageParam는 초기값이 undefined 이므로 초기값으로 0을 할당
    const getProductList = async ({ pageParam = 0 }) => {
        // 서버 요청으로 pageParam 즉, offset = 0 을 넘겨준다.
        const response = await axios.post(`/product/offset=${pageParam}/limit=10`);
        // 받아온 리스트를 객체 형태로 offset과 함께 저장한다.
        return {
            products: response.data,
            offset: pageParam,
            isLast: response.isLast,
        };
    };

    return useInfiniteQuery(["product-list"], getProductList, {
        getNextPageParam: (list) => list.pageParam + 10,
        //list는 위에서 리턴되는 객체를 가리키며,
        //getNextPageParam를 통해 다음 요청시 offset에 10을 더하여 서버 요청을 보낸다.
    });
};

getProductList

getProductList 함수의 return 값은 products, offset, isLast 이다.

products는 데이터를 담을 곳이며, offset으로 구분하여 서버에 요청한다.

위 코드에선 pageParam값으로 offset을 변경한다.

isLast는 더이상 불러올 데이터가 없을 경우 받을 응답이다. 보통 boolean 으로 구분한다.


useInfiniteQuery

3번 째 인자엔 option이 들어갈 자리로 useQuery의 옵션들을 사용 가능하고 추가적으로 getNextPageParam, getPreviousPageParam 이 두가지를 사용할 수 있는데 해당 사례에선 다음 데이터를 불러오기 위해 getNextPageParam 를 사용했다. getNextPageParam의 파라미터는 2가지로

getNextPageParam: (lastPage, allPages) => {}

lastPage, allPages를 사용할 수 있다. 해당 사례에선 lastPage를 lastPage.pageParam에 offset을 더해주는 용도로 사용한다.

여기까지가 useInfiniteQuery 내용이다.

위 useInfiniteProductListQuery 함수를 사용할 땐 컴포넌트에서 아래와 같은 형태로 사용된다.

const { data: productList, fetchNextPage } = useInfiniteProductListQuery();

fetchNextPage는 다음 서버 데이터를 불러오기 위한 함수이다. (offset + 10 을 해주기 위한 함수)



useEffect를 이용한 페이지 위치 계산

fetchNextPage은 페이지 하단에 닿았을 때 실행된다.

리액트의 useEffect를 이용하여 스크롤 최하단을 계산한 뒤 최하단일 경우 fetchNextPage를 호출한다.

//...

const handleScroll = () => {
    // 페이지의 총 높이
    const scrollHeight = document.documentElement.scrollHeight;
    // 이미 스크롤된 보이지 않는 구간의 높이
    const scrollTop = document.documentElement.scrollTop;
    // 현재 보여지는 페이지의 높이
    const clientHeight = document.documentElement.clientHeight;
    // scrollTop(이전 높이) + clientHeight(현재 높이) 가 scrollHeight(총 높이) 보다 클 경우 === 최하단일 경우
    // fetchNextPage 함수 호출.
    if (scrollTop + clientHeight >= scrollHeight) return fetchNextPage();
};

useEffect(() => {
    // 스크롤 감지 이벤트를 연결
    window.addEventListener("scroll", handleScroll);
    return () => {
        // 스크롤 함수 (handleScroll) 가 한번만 등록될 수 있도록 동작 후 제거.
        window.removeEventListener("scroll", handleScroll);
    };
});

//...
// mui - 사용

//...
return (
    <Grid container>
        {productList.pages.map((list, index) => {
            return list.products.map((product, key) => {
                return <CCProductCard product={product} key={`${index}-${key}`} />;
            });
        })}
    </Grid>
);

요청시 응답받은 데이터는 pages 배열에 쌓이게되며 쌓인 순서대로 보여지게된다.


참조 :

https://tanstack.com/query/v4/docs/guides/infinite-queries

https://medium.com/@_diana_lee/react-infinite-scroll-구현하기-fbd51a8a099f