react(nextjs)로 페이지네이션이 가능한 공통 컴포넌트를 만들어보자.
export interface ProductsResponse {
paging: Paging;
products: Product[];
}
export interface Paging {
currentPage: number;
totalPage: number;
}
컴포넌트 구조는 대략 아래와 같을 것이다.
props로는 currentPage, totalPage, 그리고 버튼 클릭 시 작동시킬 onChangePage 함수를 받으면 된다.
<컨테이너>
<제일첫페이지로가기 /> // 1페이지로 이동
<이전페이지로가기 /> // current -1 페이지로 이동
<페이지1 />
<페이지2 />
<페이지3 />
...
<페이지10 />
<다음페이지로가기 /> // current +1 페이지로 이동
<제일마지막페이지로가기 /> // totalPage로 이동
</컨테이너>
그렇다면 차근차근 만들어보자!
우선 보여줄 페이지 리스트를 계산해야 한다.
예를 들어, totalPage가 17인 경우에는 1~10, 11~17 이렇게 버튼들을 보여줘야 한다.
const currentPageCount: number =
Math.floor(totalPage / 10) > Math.floor((currentPage - 1) / 10)
? 10
: totalPage % 10;
ex) 전체 페이지를 17이라고 가정하였을 때,
Math.floor(17/10)은 2가 되고,
9페이지를 보여주는 경우 Math.floor(9/10)은 1이 되어 10개 (1~10까지 보여주기)
11페이지를 보여주는 경우 Math.floor(11/10)은 2가 되므로 7개(11~17)를 보여주어야 한다.
new Array(currentPageCount).fill(0)
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1~10 인 경우 1이 되어야 함
11~20 인 경우 11이 되어야 함
21~30 인 경우 21이 되어야 함
1 + Math.floor((currentPage - 1) / 10) * 10
이를 통해 완성된 currentPages 배열은 아래와 같다.
useMemo를 감싸줌으로써, 불필요한 연산이 계속 발생하지 않게 해둔다.
const currentPages: number[] = useMemo(() => {
const currentPageCount: number =
Math.floor(totalPage / 10) > Math.floor((currentPage - 1) / 10)
? 10
: totalPage % 10;
return new Array(currentPageCount)
.fill(0)
.map((_, i) => i + 1 + Math.floor((currentPage - 1) / 10) * 10);
}, [currentPage, totalPage]);
const changePage = (page: number) => {
onPageChange(page);
};
아래와 같은 경우는 button에 disabled 속성을 주어, 버튼이 활성화되지 않도록 해야한다.
이전페이지로 가기
, 제일 첫페이지로 가기
다음페이지로 가기
, 제일 마지막페이지로 가기
import ArrowLeftIcon from 'assets/icons/ArrowLeftIcon';
import ArrowRightIcon from 'assets/icons/ArrowRightIcon';
import DoubleArrowLeftIcon from 'assets/icons/DoubleArrowLeftIcon';
import DoubleArrowRightIcon from 'assets/icons/DoubleArrowRightIcon';
import { useMemo } from 'react';
import { Container, PageButton } from './styles';
interface PaginationProps {
currentPage: number;
totalPage: number;
onPageChange: (page: number) => void;
}
const Pagination = (props: PaginationProps) => {
const { currentPage, totalPage, onPageChange } = props;
const currentPages: number[] = useMemo(() => {
const currentPageCount: number =
Math.floor(totalPage / 10) > Math.floor((currentPage - 1) / 10)
? 10
: totalPage % 10;
return new Array(currentPageCount)
.fill(0)
.map((_, i) => i + 1 + Math.floor((currentPage - 1) / 10) * 10);
}, [currentPage, totalPage]);
const changePage = (page: number) => {
onPageChange(page);
};
return (
<Container>
<PageButton onClick={() => changePage(1)} disabled={currentPage === 1}>
<DoubleArrowLeftIcon width={16} height={16} />
</PageButton>
<PageButton
onClick={() => changePage(currentPage - 1)}
disabled={currentPage === 1}
>
<ArrowLeftIcon width={16} height={16} />
</PageButton>
{currentPages.map((page) => (
<PageButton
onClick={() => changePage(page)}
disabled={currentPage === page}
key={page}
>
{page}
</PageButton>
))}
<PageButton
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPage}
>
<ArrowRightIcon width={16} height={16} />
</PageButton>
<PageButton
onClick={() => onPageChange(totalPage)}
disabled={currentPage === totalPage}
>
<DoubleArrowRightIcon width={16} height={16} />
</PageButton>
</Container>
);
};
export default Pagination;
페이지네이션을 구현할 때에
1. 페이지 그룹 내에 보여줄 페이지 개수,
2. 페이지 그룹 내에서 제일 첫번째 페이지 넘버
이 두가지를 계산하는게 처음에 약간 헷갈렸는데
예시 숫자를 가지고 이리저리 해보다보니 방법을 찾아낼 수 있었다 !
다음 주에는 서버쪽에서 pagination을 하는 내용을 써보고자 한다,,