<Pagination
totalPageCount={Math.round(allProducts.length / PRODUCTS_LENGTH)}
limitPageCount={5}
currentPage={currentPage}
onChange={handleChangePage}
/>
- url변경과 관련된 로직은 Pagination컴포넌트와는 관심사가 다르다고 생각해 사용측인 부모 컴포넌트로 위임했습니다.
- url변경과 변경 감지는 부모 컴포넌트가 하고, 현재의 url path를 Pagination컴포넌트에 props으로 전달해 url이 변경될 때마다 리렌더링되도록 합니다.
// PaginationPage.tsx
// 필요한 부분만 간략화했습니다.
const PaginationPage: NextPage = () => {
const router = useRouter();
const { page } = router.query;
const [products, setProducts] = useState<Product[]>([]);
const [currentPage, setCurrentPage] = useState(1);
// 컨텐츠 데이터를 불러오는 함수
const fetchProducts = async (page: number) => {
...
};
// 페이지 버튼 클릭 시 url 변경
const handleChangePage = (page: number) => {
router.push(`pagination?page=${page}`, undefined, { shallow: true, scroll: true });
};
useEffect(() => {
// 페이지 변경 시
if (!page) return;
setCurrentPage(Number(page)); // 현재 페이지 상태 변경 -> Pagination리렌더링
fetchProducts(Number(page)); // 컨텐츠 데이터 새롭게 불러와 상태 변경 -> ProductList리렌더링
}, [page]);
return (
<Container>
<ProductList products={products} />
<Pagination
totalPageCount={Math.round(allProducts.length / PRODUCTS_LENGTH)}
limitPageCount={5}
currentPage={currentPage}
onChange={handleChangePage}
/>
</Container>
);
};
export default PaginationPage;
}
인자로 받아온 데이터와 range, createPagesGroupList, getCurrentGroupIndex함수를 이용해 초기값을 생성합니다.
[1,2,3,4,5], [6,7,8,9,10],[11]
)// usePagination.tsx
const range = (size: number, start: number) => {
return Array(size)
.fill(start)
.map((x, y) => x + y);
};
// 모든 페이지를 5개(limitCount)씩 그룹지은 배열 생성하기 위한 함수
const createPagesGroupList = (totalPageCount: number, limitPageCount: number) => {
const totalPagesGroupList = range(totalPageCount, 1);
const pagesGroupList = [];
for (let i = 0; i < totalPagesGroupList.length; i += limitPageCount) {
pagesGroupList.push(totalPagesGroupList.slice(i, i + limitPageCount));
}
return pagesGroupList;
};
// 현재 페이지가 속한 그룹의 index를 구하기 위한 함수
const getCurrentGroupIndex = (currentPage: number, limitPageCount: number) => {
return Math.ceil(currentPage / limitPageCount) - 1;
};
const usePagination = ({
totalPageCount,
limitPageCount,
currentPage,
onChange,
}: UsePaginationArgs) => {
const pagesGroupList = useRef<number[][]>(createPagesGroupList(totalPageCount, limitPageCount));
const currentGroupIndex = useRef<number>(getCurrentGroupIndex(currentPage, limitPageCount));
const [pages, setPages] = useState<number[]>(pagesGroupList.current[currentGroupIndex.current]);
const isFirstGroup = currentGroupIndex.current === 0;
const isLastGroup = currentGroupIndex.current === pagesGroupList.current.length - 1;
페이지 클릭 시
이전 범위 버튼 클릭 시
currentGroupIndex
가 1감소되고 이 값을 이용해 이전 범위 그룹으로 리렌더링합니다.다음 범위 버튼 클릭 시
currentGroupIndex
가 1증가되고 다음 범위 그룹으로 리렌더링합니다. // usePagination.tsx
const handleClickPage = (event: any) => {
const { textContent } = event.target;
const selectedPage = Number(textContent);
onChange(selectedPage); // 클릭한 페이지로 url변경
};
const handleClickLeft = () => {
if (isFirstGroup) return;
currentGroupIndex.current -= 1;
setPages(pagesGroupList.current[currentGroupIndex.current]); // 이전 그룹으로 ui변경
onChange(pagesGroupList.current[currentGroupIndex.current][limitPageCount - 1]); //현재 속한 그룹의 가장 마지막 페이지로 url변경
};
const handleClickRight = () => {
if (isLastGroup) return;
currentGroupIndex.current += 1;
setPages(pagesGroupList.current[currentGroupIndex.current]); // 다음 그룹으로 ui변경
onChange(pagesGroupList.current[currentGroupIndex.current][0]); //현재 속한 그룹의 가장 첫번째 페이지로 url변경
};
// Pagination.tsx
const Pagination = ({
currentPage = 1,
totalPageCount,
limitPageCount,
onChange,
}: PaginationProps) => {
const { pages, isFirstGroup, isLastGroup, handleClickPage, handleClickLeft, handleClickRight } =
usePagination({ totalPageCount, limitPageCount, currentPage, onChange });
return (
<Container>
<Button onClick={handleClickLeft} disabled={isFirstGroup}>
<VscChevronLeft />
</Button>
<PageWrapper>
{pages.map((page) => (
<Page
key={page}
selected={page === currentPage}
disabled={page === currentPage}
onClick={handleClickPage}
>
{page}
</Page>
))}
</PageWrapper>
<Button onClick={handleClickRight} disabled={isLastGroup}>
<VscChevronRight />
</Button>
</Container>
);
};
...
const Page = styled.button<PageType>`
padding: 4px 6px;
background-color: ${({ selected }) => (selected ? '#000' : 'transparent')};
color: ${({ selected }) => (selected ? '#fff' : '#000')};
font-size: 20px;
& + & {
margin-left: 4px;
}
&:disabled {
cursor: default;
}
`;