reactstrap 홈페이지에서 페이지네이션 컴포넌트를 찾아보면 단순히 UI적인 요소만 존재하고 기능적인 부분은 지원이 안된다.
페이징 알고리즘은 복잡하지만 간략하게 요약하자면 3가지 필수 요소가 존재한다. 한페이지에 몇개를 보여줄 것인가를 결정하는 pageSize, 전체 갯수인 totalSize, 현재 페이지 인덱스인 currentPage 이 세가지 이고 이 세가지 변수를 입력하면 페이지네이션을 구성해주어야 하나 reactstrap의 페이지 네이션 컴포넌트는 단지 화면 UI만 보여진다.
페이지네이션에는 만만하지 않은 로직이 있으므로 직접 구현하기보다 따라서 별도의 페이지네이션 컴포넌트를 찾아서 사용해야 한다.
여기서는 react 용 pagination 오픈소스 중 npmjs 에서 react pagination으로 가장 높은 popularity를 기록한 오픈소스인 rc-pagination 을 찾아서 적용한다.
1 |
yarn add rc-pagination |
페이지네이션에 필요한 3가지 변수를 useState에 선언한다. 초기값은 일단 페이지네이션을 확인하기 위해서 임의의 값으로 세팅한다.
1 2 3 |
const [pageSize, setPageSize] = useState(10); const [totalCount, setTotalCount] = useState(115); const [currentPage, setCurrentPage] = useState(1); |
index.js에 css 를 추가한다.
1 2 3 4 5 |
... import 'bootstrap/dist/css/bootstrap.css'; import 'rc-pagination/assets/index.css'; import './index.css'; ... |
Pagination 컴포넌트를 추가한다. github에 디폴트 샘플 페이지 소스를 참고한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import React, {useEffect, useState} from 'react'; import api from "../../utils/api"; import Pagination from 'rc-pagination'; export const Heroes = (props) => { const [heroes, setHeroes] = useState([]); const [pageSize, setPageSize] = useState(10); const [totalCount, setTotalCount] = useState(115); const [currentPage, setCurrentPage] = useState(1); useEffect(() => { getHeroes() }, []); const getHeroes = async () => { let response = await api.get('/api/user/heroes'); console.log(response); setHeroes(response.data.data); } return ( <> <div className="row"> {heroes.map(hero => ( <div className="col-12 p-1 col-sm-4 p-sm-2 col-md-3 p-md-3" key={hero.id}> <div className="card"> <img src={hero.photo ? hero.photo : process.env.PUBLIC_URL + '/images/face-black-18dp.svg'} style={{width: '100%'}} alt={hero.name}></img> <div className="card-body"> <h5 className="card-title">{hero.name}</h5> <p className="card-text">email: {hero.email}</p> </div> </div> </div> ))} </div> <Pagination total={totalCount} current={currentPage} pageSize={pageSize} /> </> ) } |
아래와 같이 화면 UI가 보여야 한다.
REST api 호출
화면에 제대로 보이는것을 확인하며 이제 페이지네이션을 지원하는 REST api를 호출해야 한다. 프로토콜은 목록가져오기의 API를 그대로 사용하면 되지만 query parameter에 start_index 와 page_size 를 추가해서 호출해야 되고, 리턴시 total 값을 받아서 state에 totalCount를 업데이트해야 한다.
- query parameter: start_index, page_size
테스트 결과를 보고 API 부분을 수정한다.
1 2 3 4 5 6 7 8 |
... const getHeroes = async () => { let response = await api.get(`/api/user/heroes?start_index=${pageSize * (currentPage - 1)}&page_size=${pageSize}`); console.log(response); setHeroes(response.data.data); setTotalCount(response.data.total); } ... |
이제 totalCount를 0으로 초기화 수정한다.
이제 남은 부분은 2, 3 페이지등 다른 페이지를 클릭시 이벤트 처리이다. 해당 컴포넌트에서 onChange 이벤트를 제공해준다. 이 이벤트는 페이지 인덱스를 첫번째 파라메터로 넘겨준다.
따라서, setCurrentPage로 바로 페이지를 세팅한다.
1 2 |
<Pagination total={totalCount} current={currentPage} pageSize={pageSize} onChange={(page) => setCurrentPage(page)} /> |
그리고, 페이지가 바뀌었으면 목록을 다시 호출해야된다. 그 부분은 어떻게 해야 하는가? 이미 목록을 가져오는 api는 useEffect에 정의하였다. 원래 useEffect는 DOM이 렌더링 될때 마다 업데이트되는데, 앞에서 최초 로딩시에만 정의하였다.
그러므로, useEffect의 두번째 파라메터에 currentPage를 넘겨서 이 값이 바뀌면 렌더링 하도록 하면 된다.
1 2 3 4 5 |
... useEffect(() => { getHeroes() }, [currentPage]); ... |
가운데 정렬
여러가지 방법이 있겠지만 bootstrap4에서 제공하는 flex 레이아웃을 이용해보겠다. 개발자 모드에서 보면 ul – li 구조이다. ul을 flex로 만들고 row 방향 정렬을 센터로 만든다.
1 2 |
<Pagination total={totalCount} current={currentPage} pageSize={pageSize} onChange={(page) => setCurrentPage(page)} className="d-flex justify-content-center" /> |
최종적인 소스는 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import React, {useEffect, useState} from 'react'; import api from "../../utils/api"; import Pagination from 'rc-pagination'; export const Heroes = (props) => { const [heroes, setHeroes] = useState([]); const [pageSize, setPageSize] = useState(10); const [totalCount, setTotalCount] = useState(0); const [currentPage, setCurrentPage] = useState(1); useEffect(() => { getHeroes() }, [currentPage]); const getHeroes = async () => { let response = await api.get(`/api/user/heroes?start_index=${pageSize * (currentPage - 1)}&page_size=${pageSize}`); console.log(response); setHeroes(response.data.data); setTotalCount(response.data.total); } return ( <> <div className="row"> {heroes.map(hero => ( <div className="col-12 p-1 col-sm-4 p-sm-2 col-md-3 p-md-3" key={hero.id}> <div className="card"> <img src={hero.photo ? hero.photo : process.env.PUBLIC_URL + '/images/face-black-18dp.svg'} style={{width: '100%'}} alt={hero.name}></img> <div className="card-body"> <h5 className="card-title">{hero.name}</h5> <p className="card-text">email: {hero.email}</p> </div> </div> </div> ))} </div> <Pagination total={totalCount} current={currentPage} pageSize={pageSize} onChange={(page) => setCurrentPage(page)} className="d-flex justify-content-center" /> </> ) } |