라우팅과 Bootstrap Pagination Component를 사용한 페이지 처리
✒️ 2025-05-28 10:29 내용 수정
- React-bootstrap의
<Pagination>Component를 사용하여useState()와useEffect()로 페이지 번호를 누를 때마다 데이터를 새로 가져오도록 설정했다. - 초기엔 url을 사용하지 않고
useState()로만 페이지 번호에 따른 렌더링을 관리했는데, 문제는 페이지가 새로 렌더링되면서activePage라는 State를 사용해서<Pagination.Item>의activeproperty를 관리할 때 계속해서 초기값으로 설정했기에 제대로 된 active 상태 관리가 이뤄지지 않았다.- 정리하다가 보니 최초 렌더링 설정인
useEffect(()=>{}, [])를 사용하면 될 것 같다.
- 정리하다가 보니 최초 렌더링 설정인
- 다른 방법을 고민하던 중 여러 reference에서도 사용하는
<Link>Router Component나<a>태그처럼 url을 기반으로 설정하면 url 정보를 가져와서activeproperty를 관리할 수 있을 것이라 생각해서 구조를 전체적으로 수정했다.
작성 흐름
- 먼저 페이지 처리를 위해 전체 데이터 수, 버튼에 표시할 페이지 수, 그리고 한 페이지에 표시할 데이터 수를 정한다.
| 전체 데이터 | 100개 |
|---|---|
| 버튼에 표시할 페이지 | 3개 |
| 한 페이지에 표시할 데이터 | 10개 |
- 모든 데이터를 표시하기 위해 필요한 전체 페이지 수를 계산한다.
totalData/limit으로 계산하고, 나누고 남는 데이터도 모두 담아야 하므로Math.ceil()을 사용해 계산 결과와 가장 가깝고 그 중에서 최소값인 정수를 얻는다.totalData%limit != 0일 때totalData/limit + 1한 것과 동일한 결과다.
const totalPage = Math.ceil(totalData/limit); // 전체 페이지 수
- Pagination의 시작점을
offset으로 설정하고,offset은 query string에 따라 바뀔 예정이므로useState()를 사용하여 State로 관리했다.useState()는 Hook과 State를,useEffect()는 useEffect 참고.- 초기값은 1로 설정하고, 초기 렌더링 때 query string을 확인하여 새로 설정해준다.
- query string의 값이 없거나 1 이하라면 초기값을 1로 고정하고, 그보다 크면 (값-1)로 설정하여 Pagination 버튼의 중앙에 활성화된 숫자가 오도록
offset을 설정했다. useSearchParams()와useHref()는 React-Router Hooks 참고.
const [searchParams, setSearchParams] = useSearchParams(); // 현재 page
const [offset, setOffset] = useState(1); // pagination 시작점
const navigate = useNavigate(); // 이동 처리(Link 대신 useNavigate() 사용)
useEffect(()=>{ // 최초 렌더링 때 offset을 query string으로 전달된 page 값을 사용해서 결정
let newOffset = !searchParams.get("page") || parseInt(searchParams.get("page") - 1) < 1 ?
1 : parseInt(searchParams.get("page") - 1);
setOffset(newOffset);
}, []);
- query string이 바뀔 때마다 렌더링을 다시 하도록
useEffect()를 설정한다.
useEffect(()=>{ // query string이 바뀌면 활성화된 버튼을 변경
setActivePage(!searchParams.get("page") ? 1 : parseInt(searchParams.get("page")));
}, [searchParams]);
- 출력할 Component를 만들 함수를 설정하고, 이 함수에서 offset과 버튼에 표시할 페이지 개수를 사용하여 출력할 Component의 수를 결정한다.
// pagination 숫자 설정
function pageBlock() {
let blockComponent = []; // 렌더링 할 Component를 담을 배열
for(let i = 0; i < blockPerPage; i++) { // blockPerPage 수 만큼만 표시
const pageNumber = offset + i; // 페이지 숫자는 시작점으로부터 blockPerPage 개수만큼
if (pageNumber <= totalPage) { // 총 페이지 수를 넘어가지 않도록 설정
blockComponent.push(
<Pagination.Item key={pageNumber}
active={pageNumber == activePage} // 페이지가 현재 활성화된 페이지랑 같다면 "active" property true
onClick={()=>{handleClickNumber(pageNumber)}}>
{pageNumber}
</Pagination.Item>
);
}
}
return blockComponent;
}
- Pagination에서 처음 번호 이동, 이전 번호로 이동, 다음 번호로 이동, 마지막 번호로 이동하는 버튼들을 눌렀을 때 번호 변경을 설정한다.
// 이전 버튼 눌렀을 때 번호 처리
function handlePrev() {
if (offset > blockPerPage) { // 시작점이 blockPerPage보다 클 때 1씩 감소
setOffset(offset - 1);
} else {
setOffset(1); // 시작점이 blockPerPage보다 작으면 1로 고정
}
}
// 다음 버튼 눌렀을 때 번호 처리
function handleNext() {
if (offset + blockPerPage < totalPage) { // (시작점 + blockPerPage)이 총 페이지보다 작으면 1씩 증가
setOffset(offset + 1);
} else {
handleLast(); // 마지막 가기 버튼과 동일하므로 아래 함수로 처리
}
}
// 마지막으로 가기 버튼 눌렀을 때 번호 처리
function handleLast() {
if (totalPage > blockPerPage) { // 총 페이지 수가 blockPerPage보다 클 때 마지막 페이지를 포함해서 3개 출력
setOffset(totalPage - blockPerPage + 1);
} else { // 작다면 1로 고정
setOffset(1);
}
}
//.. 생략
// 처음 번호로 이동 버튼은 Pagination 시작점인 offset을 1로 설정하면 끝
return(
<Pagination.First onClick={()=>{setOffset(1)}}/>
)
- 부가적인 설정까지 끝내면 url을 사용한 Pagination을 설정할 수 있다.
import 'bootstrap/dist/css/bootstrap.min.css'; // bootstrap 사용
import { useEffect, useState } from "react";
import { Pagination } from "react-bootstrap"; // react-bootstrap의 Pagination Component
import { useHref, useNavigate, useSearchParams } from "react-router-dom";
function PageTest() {
const blockPerPage = 3; // 버튼에 표시할 페이지 수
const totalData = 100; // 전체 데이터 수
const limit = 10; // 한 페이지에 표시할 데이터 수
const [searchParams, setSearchParams] = useSearchParams(); // 현재 page
const totalPage = Math.max(Math.ceil(totalData/limit), 1); // 전체 페이지 수
const [offset, setOffset] = useState(1); // pagination 시작점
const [activePage, setActivePage] = useState( // 활성화된 페이지
!searchParams.get("page") ? 1 : searchParams.get("page")
);
const url = useHref(); // 경로
const navigate = useNavigate(); // 이동 처리(Link 대신 useNavigate() 사용)
useEffect(()=>{ // 최초 렌더링 때 offset을 query string으로 전달된 page 값을 사용해서 결정
let newOffset = !searchParams.get("page") || parseInt(searchParams.get("page") - 1) < 1 ?
1 : parseInt(searchParams.get("page") - 1);
setOffset(newOffset);
}, []);
useEffect(()=>{ // query string이 바뀌면 활성화된 버튼을 변경
setActivePage(!searchParams.get("page") ? 1 : parseInt(searchParams.get("page")));
}, [searchParams]);
// 버튼 누를 때 처리
function handleClickNumber(pageNumber) {
setActivePage(pageNumber); // pagination 숫자 활성화 설정
navigate(`${url}?page=${pageNumber}`); // url을 사용한 페이지 이동 처리
}
// pagination 숫자 설정
function pageBlock() {
let blockComponent = []; // 렌더링 할 Component를 담을 배열
for(let i = 0; i < blockPerPage; i++) { // blockPerPage 수 만큼만 표시
const pageNumber = offset + i; // 페이지 숫자는 시작점으로부터 blockPerPage 개수만큼
if (pageNumber <= totalPage) { // 총 페이지 수를 넘어가지 않도록 설정
blockComponent.push(
<Pagination.Item key={pageNumber}
active={pageNumber == activePage} // 페이지가 현재 활성화된 페이지랑 같다면
//"active" property true
onClick={()=>{handleClickNumber(pageNumber)}}>
{pageNumber}
</Pagination.Item>
);
}
}
return blockComponent;
}
// 이전 버튼 눌렀을 때 번호 처리
function handlePrev() {
if (offset > blockPerPage) { // 시작점이 blockPerPage보다 클 때 1씩 감소
setOffset(offset - 1);
} else {
setOffset(1); // 시작점이 blockPerPage보다 작으면 1로 고정
}
}
// 다음 버튼 눌렀을 때 번호 처리
function handleNext() {
if (offset + blockPerPage < totalPage) {
setOffset(offset + 1); // (시작점 + blockPerPage)이 총 페이지보다 작으면 1씩 증가
} else {
handleLast(); // 마지막 가기 버튼과 동일하므로 아래 함수로 처리
}
}
// 마지막으로 가기 버튼 눌렀을 때 번호 처리
function handleLast() {
if (totalPage > blockPerPage) { // 총 페이지 수가 blockPerPage보다 클 때
setOffset(totalPage - blockPerPage + 1); // 마지막 페이지를 포함해서 3개 출력
} else { // 작다면 1로 고정
setOffset(1);
}
}
return(
<div className='d-flex justify-content-center'>
<Pagination>
{/* 첫 페이지로 돌아가기 버튼은 offset을 1로 설정한다. */}
<Pagination.First onClick={()=>{setOffset(1)}}/>
<Pagination.Prev onClick={handlePrev}/>
{
// <Pagination.Item> Component들이 반환된다.
pageBlock()
}
<Pagination.Next onClick={handleNext}/>
<Pagination.Last onClick={handleLast}/>
</Pagination>
</div>
)
}
export default PageTest;
- 버튼을 누르면 활성화 상태로 바뀌고 url의 page 값도 누른 버튼 숫자로 바뀐다.
- 다음 번호로 가기 버튼을 눌렀을 때 다음 번호가 뜬다.
- 다음 버튼을 눌렀을 때 해당 번호를 바로 활성화하도록 설정하지 않았다.
- 맨 마지막 번호로 이동하면 마지막 페이지 번호가 뜨지만 숫자 버튼을 누르지 않아 활성화 상태로 바뀌지 않고, url의 page 값도 바뀌지 않는다.
- 거의 마지막 번호를 누른 상태에서 처음 번호로 돌아가는 버튼을 누르면 1, 2, 3번이 뜬다.
- 9번을 누른 상태로 url에 page 값을 7번으로 변경하면 7번 버튼이 활성화되면서 버튼이 6, 7, 8번으로 바뀐다.