React Pagination 구현하기

yunbiyomi·2024년 2월 5일






싸커마켓의 메인 페이지에서 전체 상품들을 보여줄 때 지금은 백엔드에 의해 한 페이지 당 15개의 상품이 표시되도록 되어있다.
최초로 렌더링 될 때 뜨는 15개의 상품들 말고 다른 나머지의 상품들을 표시해주기 위해서 어떤 방법을 사용해보면 좋을지 고민해보게 되었다.

2가지 방법이 떠올랐었는데 무한 스크롤 방식과 Pagination 방식이었다.

  • 무한스크롤
    : 사용자가 페이지를 스크롤 할 때마다 자동으로 추가 데이터를 로드하기 때문에 사용자가 페이지를 탐색하는데 편리

  • Pagination
    : 사용자가 명시적으로 다음 페이지로 이동할 수 있기 때문에 사용자가 페이지를 선택하고 탐색하는데 편리


두 가지 방식을 비교해 본 결과,
무한 스크롤은 인스타그램 피드와 같이 훑으며 지나가는 SNS 서비스에 적합한 것 같고
Pagination쇼핑몰에서 사용자가 상품의 위치를 기억하고 필요할 때 쉽게 찾을 수 있도록 도와주는데 더 적합해 보인다.


그리고 SEO의 관점에서도
Pagination각 페이지의 URL이 고유하기 때문에 검색 엔진이 각 페이지를 개별적으로 색인화하여 검색 결과에 표시할 수 있도록 도와준다고한다.
반면 무한 스크롤 모든 콘텐츠가 하나의 페이지에 동적으로 로딩되기 때문에 검색 엔진이 콘텐츠를 색인화하지 못할 수 있어 SEO 측면에서 불리할 수 있다.


그래서 싸커마켓의 메인 페이지는 Pagination을 이용해 전체 상품 보기를 구현해보려고한다.





📌 Pagination


  1. 전체 페이지 수 계산하기
  2. 현재 페이지에 해당하는 상품 표시하기
  3. 페이지 버튼을 만들고, 페이지 버튼 클릭 시 해당 페이지로 이동하도록 동작 구현하기
  4. 페이지 버튼을 5개씩 나눠주기

다음과 같은 순서대로 구현하였다.



🔷 전체 페이지 수 계산하기

각 페이지마다 15개의 상품을 표시하기 위해 limit를 15로 지정하였다.
쇼핑몰의 총 상품 갯수를 저장한 totalCount를 limit로 나누고 올림하면 전체 페이지 수를 계산할 수 있다.

  const calculatePageCount = () => {
    const limit = 15;
    const count = Math.ceil(totalCount / limit);
    setPageCount(count);
  }

지금 내가 사용하는 API의 총 상품 갯수는 112개였는데 이 계산을 통해 총 8개의 페이지가 나온다는 것을 구할 수 있었다.





🔷 현재 페이지에 해당하는 상품 표시하기

axios.get 메서드를 사용하여 서버에 GET 요청을 보낼 때 ?page= 뒤에 해당 페이지의 번호를 넘겨주어 해당 페이지에 해당하는 상품이 정확히 표시될 수 있도록 하였다.

  const getProducts = async () => {
    try {
      const response = await axios.get(`products/?page=${currentPage}`);
      console.log(response.data);
      setTotalCount(response.data.count);
      setProducts(response.data.results);
      setLoad(true);
    } catch (error) {
      console.error('상품 가져오기 실패', error.response.data);
    }
  }





🔷 페이지 버튼 만들고, 각 페이지 버튼 이동 동작 구현하기

페이지네이션의 CSS는 이렇게 구현하였다.



페이지 이동 동작과 전체 페이지 수를 관리하기 위해 usePageNavigation이라는 Hook을 따로 만들어 사용했다.

import { useEffect, useState } from 'react'

const usePageNavigation = (totalCount) => {
  const[pageCount, setPageCount] = useState(1);
  const[currentPage, setCurrentPage] = useState(1);

  const calculatePageCount = () => {
    const limit = 15;
    const count = Math.ceil(totalCount / limit);
    setPageCount(count);
  }

  const handlePreviousPage = () => {
    if(currentPage > 1)
      setCurrentPage(currentPage-1);
  }

  const handleNextPage = () => {
    if(currentPage < pageCount)
      setCurrentPage(currentPage+1);
  }

  const handlePageClick = (pageNumber) => {
    setCurrentPage(pageNumber);
  }

  useEffect(() => {
    calculatePageCount();
  }, [totalCount])


  return {
    currentPage,
    pageCount,
    handlePreviousPage,
    handleNextPage,
    handlePageClick,
  };
}

export default usePageNavigation

handlePreviousPagehandleNextPage 함수는 초기에 조건을 설정하지 않았다.

이로 인해 첫 페이지에서 이전 페이지로 이동하거나 마지막 페이지에서 다음 페이지로 이동하는 문제가 발생하여 백엔드에서 해당 페이지의 상품이 없다는 에러가 발생하게 되었다.

그래서 handlePreviousPage 함수는 현재 페이지가 1페이지보다 큰 경우에만, handleNextPage 함수는 마지막 페이지보다 작은 경우에만 동작하도록 조건을 추가해주었다.





🔷 페이지 버튼을 5개씩 나눠주기

이 부분이 제일 복잡했다.

페이지의 갯수를 5개씩 나눠 1 2 3 4 5 / 6 7 8 이런식으로 표시하고 싶었다.

먼저 한 번에 보여줄 페이지 버튼의 개수를 5개로 설정하고 pageSize에 저장했다.


그런 다음 현재 페이지를 pageSize로 나눠 현재 페이지가 속한 페이지 그룹을 결정하는 currentGroup에 저장해주었다.


startPage는 현재 페이지가 속한 그룹에서 첫 번째로 보여질 페이지 번호인데
이 번호는 각 그룹의 첫 번째 페이지로서
예를 들어 1, 6, 11 ··· 등 과 같이 시작하는 페이지 번호이다.
위의 수는 0 X 5 + 1, 1 X 5 + 1, 2 X 5 + 1 ··· 이와 같으므로
(currentGroup - 1) * pageSize + 1 을 사용하여 startPage를 구했다.


endPage는 현재 페이지가 속한 그룹에서 마지막으로 보여질 페이지 번호이다.
예를 들어 5, 10, 15 ··· 등으로 끝나는 번호이기 때문에
currentGroup * pageSize을 사용해서 구했다.

여기서 중요한점
currentGroup * pageSize 값이 총 페이지의 수보다 큰 경우
총 페이지보다 더 많은 수의 페이지 버튼이 생길 수 있다.
그래서 Math.min함수를 이용하여 총 페이지의 수를 나타내는 pageCountcurrentGroup * pageSize보다 큰 경우에는 endPagecurrentGroup * pageSize로 저장하고
작은 경우에는 endPagepageCount로 저장했다.


마지막으로 Array.from을 이용하여 startPage에서 endPage까지의 범위를 배열로 생성하고
map함수를 통해 각 페이지 번호에 대한 페이지 버튼을 생성해주었다.

그리고 각 버튼을 클릭할 때 onClick 이벤트를 통해 Pagination이 제대로 동작하도록 구현했다.


const Pagination = ({ currentPage, pageCount, handlePreviousPage, handleNextPage, handlePageClick }) => {
  const pageSize = 5;
  const currentGroup = Math.ceil(currentPage / pageSize);
  const startPage = (currentGroup - 1) * pageSize + 1;
  const endPage = Math.min(currentGroup * pageSize, pageCount);

  return (
    <PaginationContainer>
      <PaginationBtn onClick={handlePreviousPage} isLeft />
      {Array.from({ length: endPage - startPage + 1 }, (_, index) => {
        const pageNumber = startPage + index;
        return (
        <PageNumberWrap key={pageNumber} isCurrentPage={currentPage === pageNumber}>
          <PageNumber onClick={() => handlePageClick(pageNumber)} isCurrentPage={currentPage === pageNumber}>
            {pageNumber}
          </PageNumber>
        </PageNumberWrap>
        )}
       )}
      <PaginationBtn onClick={handleNextPage} isRight />
    </PaginationContainer>
  );
};





🔷 구현

Pagination을 구현한 모습니다.

추후 총 페이지의 맨 처음 페이지와 마지막 페이지로 한 번에 이동할 수 있는 버튼도 추가할 예정이다.

profile
새로움을 두려워 하지 않는 도전하는 프론트엔드 개발자👩‍💻

0개의 댓글