들어가기전
해당 글은 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