[TIL #40] 무한 스크롤

차슈·2024년 6월 20일
1

TIL

목록 보기
41/70
post-thumbnail

페이지네이션

대량의 데이터를 여러 페이지로 나누어 보여주는 기법

페이지네이션의 주요 요소

페이지당 항목 수(Items per Page): 한 페이지에 표시되는 데이터 항목의 수

총 데이터 개수(Total Count): 전체 데이터의 총 개수

전체 페이지 수(Total Pages): 전체 데이터를 페이지당 항목 수로 나누어 얻은 총 페이지 수

페이지 번호(Page Number): 현재 페이지의 번호
사용자가 번호 클릭함에 따라 변경될 수 있는 값이므로 상태로 관리

다음/이전 페이지(Next/Previous Page): 사용자가 다음 또는 이전 페이지로 이동할 수 있도록 하는 것

export default function Pagination({ totalPages, page, setPage }) {
  return (
    <div
      style={{ display: "flex", justifyContent: "center", marginTop: "20px" }}
    >
      <button
        onClick={() => setPage((prev) => Math.max(prev - 1, 1))}
        disabled={page === 1}
      >
        prev
      </button>
      {[...Array(totalPages)].map((_, index) => (
        <button
          key={index + 1}
          onClick={() => setPage(index + 1)}
          style={{
            fontWeight: page === index + 1 ? "bold" : "normal",
            margin: "0 5px",
          }}
        >
          {index + 1}
        </button>
      ))}
      <button
        onClick={() => setPage((prev) => Math.min(prev + 1, totalPages))}
        disabled={page === totalPages}
      >
        next
      </button>
    </div>
  );
}

더보기

사용자 인터랙션에 따른 추가 데이터 로드 하는 UI useInfiniteQuery 를 이용
useInfiniteQuery 공식 문서

pages
queryFn 요청 시 마다 리턴값을 배열안에 누적합니다.
pageParams
queryFn 요청 시 매개변수로 넘겨준 pageParam 값을 배열안에 누적합니다.
hasNextPage
추가로 불러온 다음 페이지 있는 지 여부
getNextPageParam 함수의 리턴값이 undefined 인 경우false

다음페이지가 있을 경우에만 더보기 버튼이 노출

{hasNextPage && (
 <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
   {isFetchingNextPage ? "로딩중..." : "더보기"}
 </button>
)}

요청하는 데이터의 총 데이터 개수, 페이지 당 개수 만 알아도 다음 페이지 존재 여부를 알 수 있다.

// lastPage: 가장 최근에 받아온 페이지 데이터
// allPages: 현재까지 누적된 전체 페이지 데이터 (배열)
getNextPageParam: (lastPage, allPages, lastPageParam) => {
  const nextPage = lastPageParam + 1;
  return nextPage <= lastPage.totalPages ? nextPage : undefined;
},

무한 스크롤

intersection observer가 보편적이지만, 리액트에서는 react-intersection-observer 패키지의 useInView 훅을 이용하는 것이 좋다.

const { ref } = useInView({
    threshold: 1,
    onChange: (inView) => {
      if (inView && hasNextPage && !isFetchingNextPage) {
        fetchNextPage();
      }
    },
});

return (
<>
  <ul style={{ listStyle: "none", width: 250, backgroundColor: "beige" }}>
    {todos.map((todo, idx) => {
      const isLastItem = todos.length - 1 === idx;
      return (
        <TodoItem ref={isLastItem ? ref : null} key={todo.id} todo={todo} />
      );
    })}
  </ul>
</>
);

react-intersection-observer 공식문서

0개의 댓글