
싸커마켓의 메인 페이지에서 전체 상품들을 보여줄 때 지금은 백엔드에 의해 한 페이지 당 15개의 상품이 표시되도록 되어있다.
최초로 렌더링 될 때 뜨는 15개의 상품들 말고 다른 나머지의 상품들을 표시해주기 위해서 어떤 방법을 사용해보면 좋을지 고민해보게 되었다.
2가지 방법이 떠올랐었는데 무한 스크롤 방식과 Pagination 방식이었다.
무한스크롤
: 사용자가 페이지를 스크롤 할 때마다 자동으로 추가 데이터를 로드하기 때문에 사용자가 페이지를 탐색하는데 편리
Pagination
: 사용자가 명시적으로 다음 페이지로 이동할 수 있기 때문에 사용자가 페이지를 선택하고 탐색하는데 편리
두 가지 방식을 비교해 본 결과,
무한 스크롤은 인스타그램 피드와 같이 훑으며 지나가는 SNS 서비스에 적합한 것 같고
Pagination이 쇼핑몰에서 사용자가 상품의 위치를 기억하고 필요할 때 쉽게 찾을 수 있도록 도와주는데 더 적합해 보인다.
그리고 SEO의 관점에서도
Pagination이 각 페이지의 URL이 고유하기 때문에 검색 엔진이 각 페이지를 개별적으로 색인화하여 검색 결과에 표시할 수 있도록 도와준다고한다.
반면 무한 스크롤은 모든 콘텐츠가 하나의 페이지에 동적으로 로딩되기 때문에 검색 엔진이 콘텐츠를 색인화하지 못할 수 있어 SEO 측면에서 불리할 수 있다.
- 전체 페이지 수 계산하기
- 현재 페이지에 해당하는 상품 표시하기
- 페이지 버튼을 만들고, 페이지 버튼 클릭 시 해당 페이지로 이동하도록 동작 구현하기
- 페이지 버튼을 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
handlePreviousPage와 handleNextPage 함수는 초기에 조건을 설정하지 않았다.
이로 인해 첫 페이지에서 이전 페이지로 이동하거나 마지막 페이지에서 다음 페이지로 이동하는 문제가 발생하여 백엔드에서 해당 페이지의 상품이 없다는 에러가 발생하게 되었다.
그래서 handlePreviousPage 함수는 현재 페이지가 1페이지보다 큰 경우에만, handleNextPage 함수는 마지막 페이지보다 작은 경우에만 동작하도록 조건을 추가해주었다.
이 부분이 제일 복잡했다.
페이지의 갯수를 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함수를 이용하여 총 페이지의 수를 나타내는pageCount가currentGroup * pageSize보다 큰 경우에는endPage를currentGroup * pageSize로 저장하고
작은 경우에는endPage를pageCount로 저장했다.
마지막으로 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을 구현한 모습니다.
추후 총 페이지의 맨 처음 페이지와 마지막 페이지로 한 번에 이동할 수 있는 버튼도 추가할 예정이다.
