이전에 구현했던 페이지네이션에서...(그룹화) 기능을 추가한 버전으로 업그레이드하게 되면서 해당 내용을 정리해 보았습니다.
결과 화면 & 전체 코드
더보기
import React, { useContext, useEffect, useState } from "react"
import Image from "next/image"
import CommonContext from "@/components/CommonContext"
import { useQueryClient } from "react-query"
const Pagination = ({ queryKey, queryFn, page, setPage, totalPage }) => {
const { brandType } = useContext(CommonContext)
const queryClient = useQueryClient()
const [currentPage, setCurrentPage] = useState(1)
const [currentGroupIndex, setCurrentGroupIndex] = useState(0)
const groupDivisionStandard = 5
let firstNum = currentPage - (currentPage % 10) + 1
let lastNum = currentPage - (currentPage % totalPage) + totalPage
const _totalPage = [
...Array(totalPage)
.fill()
.map((_, i) => i + 1),
]
const division = (arr, n) => {
const length = arr.length
const divide = Math.floor(length / n) + Boolean(Math.floor(length % n))
const newArr = []
for (let i = 0; i < divide; i++) {
newArr.push(arr.splice(0, n))
}
return newArr
}
const groupArr = division([..._totalPage], groupDivisionStandard)
// 데이터가 늦게 로딩되는 것을 방지하기 위해 react-query의 prefetching을 사용하여 다음 페이지의 정보를 미리 불러옴
// 참고 : https://velog.io/@tkdals0978/React-Query-Pagination%EA%B3%BC-Prefetching
useEffect(() => {
if (page <= totalPage - 1) {
const nextPage = page + 1
queryClient.prefetchQuery([queryKey, nextPage], queryFn)
}
}, [page, queryClient])
useEffect(() => {
const groupIndex = groupArr.findIndex((item) => item.includes(page))
setCurrentGroupIndex(groupIndex)
}, [page])
const paginationMap = {
"<<": () => {
setPage(1)
setCurrentPage(1)
},
"<": () => {
page === 1
? setPage(1)
: setPage((prev) => {
return prev - 1
})
setCurrentPage(page - 2)
},
">": () => {
page === totalPage
? setPage(totalPage)
: setPage((prev) => {
return prev + 1
})
setCurrentPage(page)
},
">>": () => {
const pageNum = totalPage - 10 - ((totalPage - 10) % 10) + 10
if (pageNum === totalPage) {
setPage(totalPage)
setCurrentPage(totalPage - 1)
} else {
setPage(totalPage)
setCurrentPage(totalPage)
}
},
}
const handlePageChange = (_page) => {
if (typeof _page === "string") {
paginationMap[_page]()
} else {
setPage(_page)
}
}
return (
<PaginationCss>
<a
className={`${page === 1 ? "disabled" : undefined}`}
onClick={() => {
handlePageChange("<<")
}}
>
<div className="page-item">
<Image src="/img/pagination/chevronsRight.png" width={11} height={10} alt="chevronsRight" />
</div>
</a>
<a
className={`${page === 1 ? "disabled" : undefined}`}
onClick={() => {
handlePageChange("<")
}}
>
<div className="page-item rightGap">
<Image src="/img/pagination/chevronRight.png" width={6} height={12} alt="chevronsRight" />
</div>
</a>
{_totalPage.map((item) => {
// 첫 페이지, 총 페이지 내에서만 li가 생성되도록 설정
if (item >= firstNum && item <= lastNum) {
if (groupArr[currentGroupIndex].includes(item)) {
const isStartOfGroup = currentGroupIndex > 0 && item === groupArr[currentGroupIndex][0]
const isEndOfGroup =
currentGroupIndex < groupArr.length &&
groupArr[currentGroupIndex + 1] &&
item === groupArr[currentGroupIndex][groupDivisionStandard - 1]
return (
<React.Fragment key={item}>
{isStartOfGroup && (
<div className="group">
<a
className={`page-link ${brandType}PaginationBg`}
onClick={() => {
handlePageChange(1)
}}
>
<div className={`page-item page-item-number pointer ${item === 1 ? "active" : undefined}`}>1</div>
</a>
<Image src="/img/pagination/pagination.png" width={24} height={24} alt="paginationIcon" />
</div>
)}
<a
className={`page-link ${brandType}PaginationBg`}
onClick={() => {
handlePageChange(item)
}}
>
<div className={`page-item page-item-number pointer ${item === page ? "active" : undefined}`}>
{item}
</div>
</a>
{isEndOfGroup && (
<div className="group">
<Image src="/img/pagination/pagination.png" width={24} height={24} alt="paginationIcon" />
<a
className={`page-link ${brandType}PaginationBg`}
onClick={() => {
handlePageChange(totalPage)
}}
>
<div
className={`page-item page-item-number pointer ${item === totalPage ? "active" : undefined}`}
>
{totalPage}
</div>
</a>
</div>
)}
</React.Fragment>
)
}
}
})}
<a
className={`page-link pointer ${page === totalPage || totalPage === 1 ? "disabled" : undefined}`}
onClick={() => {
handlePageChange(">")
}}
>
<div className="page-item leftGap">
<Image
className="chevronLeft"
src="/img/pagination/chevronRight.png"
width={6}
height={12}
alt="chevronLeft"
/>
</div>
</a>
<a
className={`page-link pointer ${page === totalPage || totalPage === 1 ? "disabled" : undefined}`}
onClick={() => {
handlePageChange(">>")
}}
>
<div className="page-item">
<Image
className="chevronsLeft"
src="/img/pagination/chevronsRight.png"
width={11}
height={10}
alt="chevronsLeft"
/>
</div>
</a>
</PaginationCss>
)
}
export default Pagination
코드 설명
그룹화 페이지네이션을 구현할 때 중요한 부분이 "그룹화"이기 때문에, 이 부분을 페이지네이션에 적용하기 위해
기능 부분에서는 아래와 같이 구현하였고,
- currentGroupIndex : 현재 그룹 위치
- groupDivisionStandard : 페이지네이션 그룹화 기준 개수
- division() : 그룹화할 배열, 그룹화할 기준 개수를 인자로 받아 이중배열로 그룹화된 배열을 반환하는 함수
- groupArr : division()에서 반환된 이중배열을 담는 변수
- useEffect(() => {... }, page) : groupArr 내에 있는 각 배열에서 현재 페이지(page)를 담고 있는 배열의 index를 반환하여 currentGroupIndex에 값을 넣어주는 함수를 현재 페이지(page)가 변할 때마다 실행
JSX에서는 아래와 같이 추가적으로 조건에 따라... 이 보이도록 구현하였다.
- groupArr [currentGroupIndex]. includes(item) : 현재 그룹에 item이 있으면 true, 없으면 false + true인 경우에만 아래 코드 수행
- isStartOfGroup : currentGroupIndex(현재 그룹)이 0보다 크면서, currentGroupIndex(현재 그룹)에 0번째 값이 item과 같을 경우 true, 다르면 false
(아래와 같이 보임)
- isEndOfGroup :
- currentGroupIndex < groupIndex.length : currentGroup이 groupArr.length(그룹 배열 길이) 보다 작으면서
- groupArr [currentGroupIndex + 1] : 다음 그룹이 있으면서
- item === groupArr [currentGroupIndex][groupDivisionStandard - 1] : item이 현재 그룹(groupArr [currentGroupIndex]에 마지막 index 값([groupDivisionStandard - 1])과 같으면
- 위 조건을 모두 충족하면 true, 다르면 false
(아래와 같이 보임)
'더 알아보기 > 기능' 카테고리의 다른 글
[Mockoon] Route에 paramer 설정하기 (0) | 2024.09.25 |
---|---|
[Next.js] Drag&Drop 기능 구현하기 (0) | 2024.01.09 |
[Next.js] 드롭다운 영역 외 클릭 시 메뉴 닫기 (2) | 2023.11.27 |
[Next.js] 드래그 상태에서 css 조작하기 (2) | 2023.11.23 |
[React + Typescript] Canvas 태그와 useRef를 사용하여 그림판 만들기 (0) | 2023.10.20 |
댓글