[Next.js] Pagination - React Query(Infinite Queries)

서민지·2024년 6월 1일
0

Next.js

목록 보기
8/8

Infinite Queries

  • data: Infinite Query 결과 데이터
  • data.pages: 가져온 페이지들의 배열
  • data.pageParams: 페이지를 가져오기 위한 페이지 매개 변수.배열의 형태
  • fetchNextPage, fetchPreviousPage: 다음 페이지/이전 페이지의 데이터를 가져오는 함수
  • getNextPageParam, getPreviousPageParam: 다음/이전 페이지에 대한 매개 변수를 생성하는 함수
  • hasNextPage, hasPreviousPage: 다음/이전 페이지가 있는지 여부 나타내는 boolean 값
  • isFetchingNextPage, isFetchingPreviousPage: 다음/이전 페이지의 데이터를 가져오는동안 로딩 상태를 나타내는 boolean 값

Intersection Observer

브라우저 viewport와 원하는 요소의 교차점을 관찰하며, 요소가 viewport에 포함 되는지 구별하는 기능

  • 비동기적으로 실행 -> 메인 스레드에 영향을 주지 않고 요소들의 변경사항 관찰
    • Scroll 및 getBoundingClientReact의 성능 문제 해결
  • IntersectionObserverEntry 속성을 활용해서 요소들의 위치를 알 수 있음
  • 다양하게 활용 가능
    • 페이지 스크롤 되는 도중에 발생하는 이미지 지연 로딩
    • 자동으로 페이지 하단에 스크롤 했을 때 무한스크롤 구현
    • 광고 수익 계산을 위한 광고 가시성 보고

무한스크롤 설계하기

  1. Intersection Observer을 이용해서 페이지 하단 ref에 도달했을 때
  2. React query의 useInfiniteQuery 함수를 이용해서 0.5초 뒤에 fetchNextPage() 함수 호출
  3. 다음 페이지 데이터가 없다면 페이지 수 올라가지 않도록 처리

src/hooks/useIntersectionObserver.tsx

Intrersection Observer사용을 위한 hook 만들어주기

import { RefObject, useEffect, useState } from "react";

function useIntersectionObserver(
 elementRef: RefObject<Element>,
 { threshold = 0.1, root = null, rootMargin = "0%" }
) {
 const [entry, setEntry] = useState<IntersectionObserverEntry>();

 const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
  setEntry(entry);
 };

 useEffect(() => {
  const node = elementRef?.current;
  const hasIOSupport = !!window.IntersectionObserver;

  if (!node || !hasIOSupport) return;

  const observerParams = { threshold, root, rootMargin };
  const observer = new IntersectionObserver(updateEntry, observerParams);

  observer.observe(node);

  return () => observer.disconnect();
 }, [elementRef?.current, root, rootMargin, JSON.stringify(threshold)]);

 return entry;
}

export default useIntersectionObserver;

리스트 페이지에 무한스크롤 적용하기

(useIntersectionObserver 부분만 코드 포스팅)

...

 const ref = useRef<HTMLDivElement | null>(null);
 const pageRef = useIntersectionObserver(ref, {});
 const isPageEnd = !!pageRef?.isIntersecting;

 const [pageEnd, setPageEng] = useState<boolean>(false);

 const fetchProjects = async ({ pageParam = "1" }) => {
  const { data } = await axios("/api/stores?page=" + pageParam, {
   params: {
    limit: 10,
    page: pageParam,
   },
  });
  return data;
 };
 
 
 const {
  data: stores,
  isFetching,
  fetchNextPage,
  isFetchingNextPage,
  hasNextPage,
  isError,
  isLoading,
 } = useInfiniteQuery("stores", fetchProjects, {
  getNextPageParam: (lastPage: any) =>
   lastPage.data?.length > 0 ? lastPage.page + 1 : undefined,
 });

 const fetchNext = useCallback(async () => {
  const res = await fetchNextPage();

  if (res.isError) {
   console.log(res.error);
  }
 }, [fetchNextPage]);

 useEffect(() => {
  let timerId: NodeJS.Timeout | undefined;
  if (isPageEnd && hasNextPage) {
   timerId = setTimeout(() => {
    fetchNext();
   }, 500);
  }

  return () => clearTimeout(timerId);
 }, [fetchNext, hasNextPage, isPageEnd]);
 
 
 return (
 ...생략...
 
    {(isFetching || hasNextPage || isFetchingNextPage) && <Loader />}
   <div className="w-full touch-none h-10 mb-10 " ref={ref} />
 )
 ...
 
 
 
profile
Do what I want for no regret

0개의 댓글

관련 채용 정보