React Pagination 구현하기
리스트를 띄워줄 페이지를 만드는 과정에서 Pagination이 필요했다.
처음엔 Material-UI에서 제공하는 Pagination 컴포넌트를 이용해서 구현하려 했으나 문제가 있었다. 삽질끝에 포기하고 결국 다른 패키지를 사용했다.
📦 Material-UI (비추)
다음은 Material-UI에서 제공하는 데모 컴포넌트이다.
https://material-ui.com/components/pagination/#pagination
겉보기엔 무척 예뻐보인다. 하지만, 큰 문제가 있다.
Material-UI에서 제공하는 Pagination 컴포넌트는 무조건 첫번째 페이지 숫자와 마지막 페이지 숫자를 띄워준다.
이게 무슨 말이냐면...
일반적으로 < 1 2 3 4 5 > 이렇게 생긴 페이지네이션 컴포넌트가 있을 때, 세번째 페이지에서 ">" 버튼을 누르면 < 3 4 5 6 7 > 이렇게 바뀌는 것이 정상이다.
그런데, Material-UI에서 제공하는 컴포넌트는 < 1 ... 3 4 5 ... 9 > 이런 식으로 된다. 누가 이런 Paginator를 쓰고 싶어할까... 아무튼 어떻게든 수정해보려고 열심히 구글링했지만, 방법을 도저히 못찾았다. StackOverflow에서 찾아보니 불가능하다는 답변이 있었다. 정 바꾸고 싶다면 모듈을 직접 뜯어서 고쳐야 하는... 그럴 바엔 직접 구현하는 편이 낫지 않을까 싶어서 포기했다.
📖 react-js-pagination (강추)
https://www.npmjs.com/package/react-js-pagination
여러 패키지를 찾아보면서 가장 사용하기 쉬워 보이는 react-js-pagination을 사용하기로 했다.
rc-pagination이 페이지네이션 관련된 패키지 중 weekly downloads는 압도적으로 많고 지속적으로 업데이트를 해주므로 이 패키지를 사용하는 편도 좋다. 어차피 메소드명만 다르고 기본적인 원리는 같으므로 원하는 패키지를 취사선택하면 될 것 같다.
Install
$ npm i react-js-pagination
Functional Component 작성하기
document 에는 클래스형 컴포넌트로 되어 있어, 다음과 같이 hooks를 사용하여 함수형 컴포넌트로 바꿨다.
예제엔 prevPageText와 nextPageText가 없는데, prev/next arrow가 너무 못생겨서 임의로 추가했다.
import React, { useState } from "react";
import './Paging.css';
import Pagination from "react-js-pagination";
const Paging = () => {
const [page, setPage] = useState(1);
const handlePageChange = (page) => {
setPage(page);
};
return (
<Pagination
activePage={page}
itemsCountPerPage={10}
totalItemsCount={450}
pageRangeDisplayed={5}
prevPageText={"‹"}
nextPageText={"›"}
onChange={handlePageChange}
/>
);
};
export default Paging;
- activePage: 현재 페이지
- itemsCountPerPage: 한 페이지당 보여줄 리스트 아이템의 개수
- totalItemsCount: 총 아이템의 개수
- pageRangeDisplayed: Paginator 내에서 보여줄 페이지의 범위
- prevPageText: "이전"을 나타낼 텍스트 (prev, <, ...)
- nextPageText: "다음"을 나타낼 텍스트 (next, >, ...)
- onChange: 페이지가 바뀔 때 핸들링해줄 함수
다음과 같이 작성하고 설레는 마음으로 react를 켜보자
?????????????????????
CSS 추가하기
그렇다. 다른 npm 패키지에서 제공하는 컴포넌트들이 그러하듯, css를 적용하지 않으면 이렇게 못생긴 Paginator가 우리를 맞이한다. react-js-pagination의 장점 중 하나는 className이 굉장히 단순한 편이라 커스터마이징하기가 좋다는 것이다.
약간의 css를 더해보자.
.pagination {
display: flex;
justify-content: center;
margin-top: 15px;
}
ul {
list-style: none;
padding: 0;
}
ul.pagination li {
display: inline-block;
width: 30px;
height: 30px;
border: 1px solid #e2e2e2;
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
}
ul.pagination li:first-child{
border-radius: 5px 0 0 5px;
}
ul.pagination li:last-child{
border-radius: 0 5px 5px 0;
}
ul.pagination li a {
text-decoration: none;
color: #337ab7;
font-size: 1rem;
}
ul.pagination li.active a {
color: white;
}
ul.pagination li.active {
background-color: #337ab7;
}
ul.pagination li a:hover,
ul.pagination li a.active {
color: blue;
}
.page-selection {
width: 48px;
height: 30px;
color: #337ab7;
}
이렇게 봐줄만한 Paginator가 탄생했다👏👏
Pagination 기능 확인하기
그렇다면 pagination이 잘 이루어지는지 handlePageChange 함수에서 로그를 찍어보도록 하자.
...
const handlePageChange = (page) => {
setPage(page);
console.log(page);
};
...
아주 잘 된다. Paginator도 잘 동작하고 상태도 잘 바뀐다!
Pagination을 구현하는 것이 목적이었다면 여기까지가 끝이다!
아래에선 API를 이용해 실제로 데이터를 띄워주는 로직을 설명하고자 한다.
API 연동까지 마친다면 다음과 같이 데이터를 뿌려줄 수 있다.
📡 API 연동하기
컨테이너에서 모든 로직을 처리하고, 컴포넌트에선 넘겨받은 props로 데이터를 띄워주는 역할만 하도록 수정하자.
다음과 같이 컴포넌트를 수정하자.
import React from "react";
import './Paging.css';
import Pagination from "react-js-pagination";
const Paging = ({page, count, setPage}) => {
return (
<Pagination
activePage={page}
itemsCountPerPage={5}
totalItemsCount={count}
pageRangeDisplayed={5}
prevPageText={"‹"}
nextPageText={"›"}
onChange={setPage}
/>
);
};
export default Paging;
그리고 리스트와 Paginator를 띄워줄 컨테이너를 작성한다.
여기서부턴 각자의 코드에 맞춰서 작성하면 된다. 나는 redux-thunk를 사용해서 리덕스 모듈 내에서 비동기 통신을 해줬고, 그 부분은 범위를 넘어가는 내용이라 생략했다. API 통신 부분이 궁금한 사람들을 위해 링크를 남긴다 => (API 통신)
여기서 EventList 컴포넌트는 배열을 props로 받아 리스트의 형태로 띄워줄 컴포넌트이다.
리스트 컴포넌트는 다음과 같이 만들 수 있다. 아래 코드의 list는 우리가 받아온 데이터가 될 것이다.
import React from 'react';
const list = [
{
id: 'a',
firstname: 'Robin',
lastname: 'Wieruch',
year: 1988,
},
...
];
const ComplexList = () => (
<ul>
{list.map(item => (
<li key={item.id}>
<div>{item.id}</div>
<div>{item.firstname}</div>
<div>{item.lastname}</div>
<div>{item.year}</div>
</li>
))}
</ul>
);
export default ComplexList;
page를 Paginator 바깥으로 뺀 이유는 데이터와 관련된 로직을 컴포넌트에서 Pagination 컴포넌트(여기선 Paging)에서 분리하기 위함이다.
- count: 총 아이템의 개수 (totalItemsCount)
- page: 현재 페이지
- items: 가져올 아이템, 배열 형태
Container
...
const ManageEvents = ({ match, history }) => {
const dispatch = useDispatch();
const { count, page, items } = useSelector(
({ event }) => ({
count: event.count,
page: event.page,
itemts: event.items,
}),
shallowEqual
);
useEffect(() => {
dispatch(eventActions.getEvents());
}, [])
const setPage = useCallback(
(page) => {
dispatch(eventActions.getEvents(page));
},
[dispatch]
);
return (
<div>
<EventList events={items} match={match}/>
<Paging page={page} count={count} setPage={setPage}/>
</div>
);
};
export default ManageEvents;
Component
import React from "react";
import './Paging.css';
import Pagination from "react-js-pagination";
const Paging = ({page, count, setPage}) => {
return (
<Pagination
activePage={page}
itemsCountPerPage={5}
totalItemsCount={count}
pageRangeDisplayed={5}
prevPageText={"‹"}
nextPageText={"›"}
onChange={setPage}
/>
);
};
export default Paging;
API EndPoint
API Endpoint는 이런 방식으로 구성된다.
여기서 page는 말 그대로 페이지, size는 한 페이지 내에서 띄워줄 아이템의 개수이다.
- /api/tutorials?page=1&size=5
- /api/tutorials?size=5: using default value for page
- /api/tutorials?page=1: using default value for size
- /api/tutorials?title=data&page=1&size=3: pagination & filter by title containing ‘data’
구현 로직
1. 처음 페이지가 로딩되면 컨테이너의 useEffect를 통해 첫번째 페이지의 아이템을 가져온다.
2. 페이지가 바뀌면 API 통신을 통해 데이터를 가져온다
3. 이때 데이터엔 총 아이템의 개수(count)와 실제 아이템(items)이 담겨있고, 이를 상태값에 저장한다.
{
count: 30,
items: [...],
}
4. 컨테이너에서 page, count, items를 props의 형태로 Pagination 컴포넌트에 전달한다.
보너스(Pagination Query)
서버쪽에서 Pagination은 다음과 같이 처리해줄 수 있다.
const { size, page } = req.query;
const offset = size*page - size;
const [ results ] = await pool.query(`
SELECT COUNT(*) AS count .. // count(totalItemsCount) 구하기
...;
SELECT
no, name, content, ... // SELECT할 column들
LIMIT ?
OFFSET ?;`,
[..., count, offset]);
...
🤛 결론
- Material-UI에서 제공하는 Pagination 컴포넌트는 절대로 쓰지말자.
- 직접 구현하기 귀찮은 사람들은 react-js-pagination이나 rc-pagination 등 다른 npm 패키지를 사용하도록 하자.
- Size, Page, Count 이 세 가지 개념만 익히면 금방 구현할 수 있다.
reference
https://bezkoder.com/react-pagination-material-ui/
https://stackoverflow.com/questions/60573914/react-pagination-styling
https://www.robinwieruch.de/react-list-component
'웹 > React.js' 카테고리의 다른 글
[React.js] 좋아요 하트 아이콘 구현하기 (0) | 2021.05.18 |
---|---|
[React.js] 라우터를 이용한 접근 제한 구현 (Access Control & Authentication) (5) | 2021.04.22 |
[React.js] 간략한 리덕스(Redux) 정리 (0) | 2021.04.20 |