콘텐츠를 한 번에 로드하지 않고, 사용자가 페이지를 아래로 스크롤할 때 필요한 만큼만 추가로 로드하는 방식
const GoalsDetailOptions = (): UseInfiniteQueryOptions<
GoalsDetailResponse,
AxiosError,
BaseInfiniteQueryResponse<GoalsDetailResponse[]>
> => ({
queryKey: [QUERY_KEYS.ALL_GOALS],
queryFn: ({ pageParam = 0 }) =>
GET<GoalsDetailResponse>(
`${API_ENDPOINTS.GOAL.ALL_GOALS}?lastGoalId=${pageParam}&size=5`,
),
getNextPageParam: (lastPage) => {
const nextCursor = lastPage.data.nextCursor;
return nextCursor !== 0 ? nextCursor : undefined;
},
initialPageParam: 0,
});
export const useGoalsDetailQuery = () => {
const { data, ...etc } = useInfiniteQuery(GoalsDetailOptions());
const goals = data?.pages.flatMap((page) => page.data.content) ?? [];
return { goals, ...etc };
};
lastGoalId
값을 포함해 5개의 데이터를 불러온다.
size
값에 따라 몇 개의 목표를 가져올 것인지 설정한다.
마지막 데이터의 nextCursor
값을 가지고 pageParam
값을 설정해준다.
nextCurosr
의 값이 null
이면 pageParam
을 undefined
로 설정하여 추가 요청을 불러오지 않는다.
초기 pageParam
값을 설정한다.
import { useEffect, useRef } from 'react';
interface InfiniteScrollProps {
fetchNextPage: () => void;
isLoading: boolean;
}
export const useInfiniteScroll = ({
fetchNextPage,
isLoading,
}: InfiniteScrollProps) => {
const observerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!observerRef.current) return;
const observerCallback: IntersectionObserverCallback = (entries) => {
const lastEntry = entries[0];
if (lastEntry.isIntersecting && !isLoading) {
fetchNextPage();
}
};
const observer = new IntersectionObserver(observerCallback, {
threshold: 1.0,
});
if (observerRef.current) {
observer.observe(observerRef.current);
}
observer.observe(observerRef.current);
return () => observer.disconnect();
}, [fetchNextPage, isLoading]);
return {
observerRef,
};
};
Intersection Observer
를 사용해 무한 스크롤을 구현하였다.
observerCallBack
을 통해 마지막 요소가 뷰포트에 들어왔는지 감지isintersecting
값이 true
이고, 데이터 로드 중이 아니라면 fetchNextPage
를 호출참고
올리브영 테크블로그
https://oliveyoung.tech/2023-10-04/useInfiniteQuery-scroll/
무한 스크롤 장단점
https://brunch.co.kr/@joohyup1001/50