오늘은 Pagination(페이지 네이션) 기능을 만들어 보았다.
라이브러리 대신 부트스트랩으로 CSS 입혔고, 기능은 JS 사용해서 만들었다.
(라이브러리를 사용하지 않은 이유 : 블로그를 찾아보던 중 react-js-pagination이라는 라이브러리를 테스트해보았는 데
CSS가 안 먹히거나 먹혀도 기능이 동작을 안 하는 경우가 생겨서 그냥 만들어 보았다.)
구현 화면
코드 (설명은 주석으로 기재함)
// /pages/test.js
import Table from "@/components/layout/table/Table"
import Pagination from "@/elements/Pagination"
import { useState } from "react"
const Test = () => {
const [page, setPage] = useState(1)
// 임시 데이터
const list = [
{
idx: 1,
이름: "오떙떙",
전화번호: "01012341234",
문의지점: "어딘가",
알게된경로: "인스타그램",
등록일시: "2022-11-08",
신규등록여부: true,
},
{
idx: 2,
이름: "오댕댕",
전화번호: "01012341234",
문의지점: "어딘가",
알게된경로: "페이스북",
등록일시: "2022-11-08",
신규등록여부: false,
},
{
idx: 3,
이름: "오댕댕",
전화번호: "01012341234",
문의지점: "어딘가",
알게된경로: "페이스북",
등록일시: "2022-11-08",
신규등록여부: false,
},
{
idx: 4,
이름: "오댕댕",
전화번호: "01012341234",
문의지점: "어딘가",
알게된경로: "페이스북",
등록일시: "2022-11-08",
신규등록여부: false,
},
{
idx: 5,
이름: "오댕댕",
전화번호: "01012341234",
문의지점: "어딘가",
알게된경로: "페이스북",
등록일시: "2022-11-08",
신규등록여부: false,
},
{
idx: 6,
이름: "오댕댕",
전화번호: "01012341234",
문의지점: "어딘가",
알게된경로: "페이스북",
등록일시: "2022-11-08",
신규등록여부: false,
},
{
idx: 7,
이름: "오댕댕",
전화번호: "01012341234",
문의지점: "어딘가",
알게된경로: "페이스북",
등록일시: "2022-11-08",
신규등록여부: false,
},
{
idx: 8,
이름: "오댕댕",
전화번호: "01012341234",
문의지점: "어딘가",
알게된경로: "페이스북",
등록일시: "2022-11-08",
신규등록여부: false,
},
{
idx: 9,
이름: "오댕댕",
전화번호: "01012341234",
문의지점: "어딘가",
알게된경로: "페이스북",
등록일시: "2022-11-08",
신규등록여부: false,
},
{
idx: 10,
이름: "오댕댕",
전화번호: "01012341234",
문의지점: "어딘가",
알게된경로: "페이스북",
등록일시: "2022-11-08",
신규등록여부: false,
},
{
idx: 11,
이름: "오댕댕",
전화번호: "01012341234",
문의지점: "어딘가",
알게된경로: "페이스북",
등록일시: "2022-11-08",
신규등록여부: false,
},
]
return (
<div>
{/*
- sideBarType : 생략 가능
- list (array) : 출력할 데이터 목록
- page (number) : 현재 페이지
*/}
<Table sideBarType="personalInquiry" list={list} page={page} />
{/*
- list (array) : 출력할 데이터 목록
- page (number) : 현재 페이지
- setPage (function) : 현재 페이지 수정 함수
- totalPage (number) : 페이징 수
(페이지가 하나만 필요할 때는 1 넘겨주고,
하나 이상일 때는 list에서 10씩 끊어서 반올림 해주면 된다.)
*/}
<Pagination
page={page}
setPage={setPage}
totalPage={list ? Math.ceil(list.length / 10) : 1}
/>
</div>
)
}
export default Test
// /elements/Table.js
import { useEffect, useState } from "react"
import Image from "next/image"
import Router from "next/router"
import styles from "@/styles/Table.module.css"
const Table = (props) => {
const { sideBarType, list, page, slice = 10 } = props
// slice를 넣어주면 그에 따라 한 페이지당 목록 잘라주고, 없을 경우 10으로 설정
const theads = [
"이름",
"전화번호",
"문의 지점",
"알게 된 경로",
"등록일시",
"신규 등록 여부",
]
const tbodys = [
"이름",
"전화번호",
"문의지점",
"알게된경로",
"등록일시",
"신규등록여부",
]
const [forwardPage, setForwardPage] = useState(1)
const [finalPage, setFinalPage] = useState(slice)
const onClickHandler = () =>
// 상세 페이지 이동 기능...
}
useEffect(() => {
/*
ex, 1 페이지에서는 idx가 1 ~ 10까지 출력
2 페이지에서는 idx가 11 ~ 20까지 출력
3 페이지에서는 idx가 21 ~ 30까지 출력
=> 위 과정에서 1, 11, 21을 forwardPage state로, 10, 20, 30을 finalPage state로 구현
반복되는 과정을 공식으로 생각하여
-> forwardPage state에서는 현재 페이지가 1일 경우 1 부여, 나머지 경우에는 page * 10 - 9를 해줌
(나머지 경우에 finalPage + 1이 아닌 이유는 >, >>일 때는 문제가 없지만 <, <<일 경우 forwardPage가 finalPage 보다 커지기 때문에 안된다.)
-> finalPage state에서는 현재 페이지 * 10을 해줌
+ page가 변할 때마다 forwardPage state와 finalPage state를 다시 계산해줌
+ (slice - 1) : 10개씩 끊는 경우는 9를 빼줘야 11 ~ 20로 출력되는 데 경우에 따라 달라지기 때문에 (slice - 1)로 구현
*/
let _forwardPage = page === 1 ? 1 : page * slice - (slice - 1)
let _finalPage = page * slice
setForwardPage(_forwardPage)
setFinalPage(_finalPage)
}, [page])
return (
<div>
<table className={`table table-hover ${styles.table}`}>
<thead>
<tr>
{theads.map((head) => {
return (
<th key={head} scope="col">
{head}
</th>
)
})}
</tr>
</thead>
<tbody className="pointer">
{list.map((data, i) => {
// 비교 할 때 forwardPage <= i + 1 <= finalPage 이렇게 하면 정확한 계산이 안됨 아래와 같이 해야 함
if (forwardPage <= i + 1 && i + 1 <= finalPage) {
return (
<tr key={data.idx} onClick={onClickHandler}>
{tbodys.map((body) => {
return (
<td key={body}>
{data[body]}
</td>
)
})}
</tr>
)
}
})}
</tbody>
</table>
</div>
)
}
export default Table
// /elements/Pagination.js
import Head from "next/head"
const Pagination = ({ page, setPage, totalPage }) => {
// if문 대신 mapping 관계로 함수를 빼서 사용
const paginationMap = {
"<<": () => {
setPage(1)
},
"<": () => {
page === 1
? setPage(1)
: setPage((prev) => {
return prev - 1
})
},
">": () => {
page === totalPage
? setPage(totalPage)
: setPage((prev) => {
return prev + 1
})
},
">>": () => {
setPage(totalPage)
},
}
const handlePageChange = (_page) => {
if (typeof _page === "string") {
paginationMap[_page]()
} else {
setPage(_page)
}
}
return (
<>
<nav aria-label="Page navigation example">
<ul className="pagination">
<li className="page-item">
<a
className={`page-link pointer ${
page === 1 ? "disabled" : undefined
}`}
onClick={() => {
handlePageChange("<<")
}}
>
{"<<"}
</a>
</li>
<li className="page-item">
<a
className={`page-link pointer ${
page === 1 ? "disabled" : undefined
}`}
onClick={() => {
handlePageChange("<")
}}
>
{"<"}
</a>
</li>
{/*
Number 형태로 totalPage를 받아오기 때문에 .map()을 사용하기 위해서 Array로 만듦
이때 고유값을 주기 위해 {idx: i + 1}을 부여함
*/}
{[
...Array(totalPage)
.fill()
.map((arr, i) => {
return { idx: i + 1 }
}),
].map((item, i) => {
return (
<li
className={`page-item pointer ${
item.idx === page ? "active" : undefined
}`}
key={item.idx}
>
<a
className="page-link"
onClick={() => {
handlePageChange(item.idx)
}}
>
{item.idx}
</a>
</li>
)
})}
<li className="page-item">
<a
className={`page-link pointer ${
page === totalPage ? "disabled" : undefined
}`}
onClick={() => {
handlePageChange(">")
}}
>
{">"}
</a>
</li>
<li className="page-item">
<a
className={`page-link pointer ${
page === totalPage ? "disabled" : undefined
}`}
onClick={() => {
handlePageChange(">>")
}}
>
{">>"}
</a>
</li>
</ul>
</nav>
</>
)
}
export default Pagination
결론적으로는 팀장님이 백엔드에서 작업해주셔서 위 기능을 사용하지는 않았지만 한번 시도해봤다는 것에 의의를 두었다!
+ 2023.02.14
: 백엔드에서 넘겨주는 totalPage를 가지고 페이지네이션 구현 (페이지네이션은 기본적으로 4개씩 끊어서 사용)
import { useContext, useState } from "react"
import CommonContext from "@/components/CommonContext"
const Pagination = ({ page, setPage, totalPage }) => {
const { brandType } = useContext(CommonContext)
const [currentPage, setCurrentPage] = useState(1)
let firstNum = currentPage - (currentPage % 4) + 1
let lastNum = currentPage - (currentPage % 4) + 4
const _totalPage = [
...Array(totalPage)
.fill()
.map((arr, i) => {
return { idx: i + 1 }
}),
]
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 - 4 - ((totalPage - 4) % 4) + 4
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 (
<>
<nav aria-label="Page navigation example">
<ul className="pagination">
<li className="page-item">
<a
className={`page-link pointer ${page === 1 ? "disabled" : undefined}`}
onClick={() => {
handlePageChange("<<")
}}
>
{"<<"}
</a>
</li>
<li className="page-item">
<a
className={`page-link pointer ${page === 1 ? "disabled" : undefined}`}
onClick={() => {
handlePageChange("<")
}}
>
{"<"}
</a>
</li>
{/*
Number 형태로 totalPage를 받아오기 때문에 .map()을 사용하기 위해서 Array로 만듦
이때 고유값을 주기 위해 {idx: i + 1}을 부여함
*/}
{_totalPage.map((item, i) => {
if (item.idx >= firstNum && item.idx <= lastNum) {
return (
<li
key={item.idx}
className={`page-item page-item-number pointer ${
item.idx === page ? "active" : undefined
}`}
>
<a
className={`page-link ${brandType}PaginationBg`}
onClick={() => {
handlePageChange(item.idx)
}}
>
{item.idx}
</a>
</li>
)
}
})}
<li className="page-item">
<a
className={`page-link pointer ${page === totalPage ? "disabled" : undefined}`}
onClick={() => {
handlePageChange(">")
}}
>
{">"}
</a>
</li>
<li className="page-item">
<a
className={`page-link pointer ${page === totalPage ? "disabled" : undefined}`}
onClick={() => {
handlePageChange(">>")
}}
>
{">>"}
</a>
</li>
</ul>
</nav>
</>
)
}
export default Pagination
'더 알아보기 > 기능' 카테고리의 다른 글
css 전역화 시키기 1️⃣ (ver. ThemeProvider) (2) | 2023.01.06 |
---|---|
크롬 비밀번호 자동완성 비활성화 (0) | 2022.12.13 |
반응형 사이드바 구현하기 (0) | 2022.11.07 |
[React] IntersectionObserver를 사용하여 스크롤 애니메이션 구현하기 (0) | 2022.11.04 |
[JS] IntersectionObserver 사용해보기 (0) | 2022.11.04 |
댓글