1. 페이지네이션 설계안
요구사항
- 한 페이지에 9개의 데이터를 보여준다.
- 페이지 하단에 점(dot) 형태의 페이지네이션 UI를 표시한다.
- 한 번에 최대 5개의 점만 표시한다.
- 다음/이전 버튼으로 페이지를 이동하면 점이 슬라이드처럼 움직인다.
- 점을 클릭하면 해당 페이지로 이동한다.
2. 기본 프로토타입 구현
초기 구현은 단순하게 현재 페이지 기준으로 start/end 계산 후 점 배열을 만들고,
이전/다음 버튼, 점 클릭 시 onChange()로 페이지 변경 요청을 하는 구조였습니다.
type Props = {
currentPage: number;
totalPages: number;
onChange: (page: number) => void;
window?: number; // 보이는 점 개수
};
export default function Pagination({ currentPage, totalPages, onChange, window = 5 }: Props) {
if (totalPages <= 1) return null;
const half = Math.floor(window / 2);
let start = Math.max(0, currentPage - half);
let end = Math.min(totalPages - 1, start + window - 1);
start = Math.max(0, Math.min(start, end - window + 1));
const pages = [];
for (let p = start; p <= end; p++) pages.push(p);
return (
<div>
<button onClick={() => onChange(currentPage - 1)}>이전</button>
{pages.map((p) => (
<button key={p} onClick={() => onChange(p)} />
))}
<button onClick={() => onChange(currentPage + 1)}>다음</button>
</div>
);
}
3. 발생한 문제
증상
- 다음 버튼 클릭 시 점이 이동하지 않음
- 점 클릭 시 UI가 갱신되지 않음
- 간혹 버튼 클릭 시 페이지가 리프레시되는 현상 발생
원인 분석
- 상태(state) 갱신 누락 : 클릭 이벤트가 발생해도 currentPage가 변경되지 않았습니다.부모 컴포넌트에서 onChange를 setCurrentPage와 연결하지 않아 클릭 이벤트가 발생해도 currentPage가 변경되지 않았습니다.
- 버튼 기본 동작 문제 : <form> 안에 페이지네이션이 있을 경우, 기본 <button>의 type이 "submit"이라 클릭 시 페이지가 새로고침됐습니다.
- 창(window) 슬라이드 로직 미비4 → 5로 넘어갈 때 점의 시작 위치가 갱신되지 않아 점이 고정되어 보였습니다.
4. 버그 수정 KEY.
(1) 부모와 상태 연결
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onChange={(p) => setCurrentPage(p)} // 필수
/>
(2) 슬라이드 방식 start/end 계산
window 단위 블록으로 페이지를 계산하여,
5개의 점만 유지하면서 블록 단위로 슬라이드되도록 수정했습니다.
const windowIndex = Math.floor(currentPage / window);
const start = windowIndex * window;
const end = Math.min(start + window - 1, totalPages - 1);
5. 최종 구현 코드
export default function Pagination({ currentPage, totalPages, onChange, window = 5 }: Props) {
if (totalPages <= 1) return null;
const windowIndex = Math.floor(currentPage / window);
const start = windowIndex * window;
const end = Math.min(start + window - 1, totalPages - 1);
const pages: number[] = [];
for (let p = start; p <= end; p++) pages.push(p);
const go = (p: number) => {
if (p < 0 || p > totalPages - 1 || p === currentPage) return;
onChange(p);
};
return (
<div className="w-full flex items-center justify-center gap-4 py-6">
<button type="button" onClick={() => go(currentPage - 1)} disabled={currentPage === 0}>
‹ 이전
</button>
<div className="flex items-center gap-3">
{pages.map((p) => (
<button
type="button"
key={p}
onClick={() => go(p)}
className={p === currentPage ? 'bg-blue-10' : 'bg-grey-10'}
/>
))}
</div>
<button
type="button"
onClick={() => go(currentPage + 1)}
disabled={currentPage >= totalPages - 1}
>
다음 ›
</button>
</div>
);
}
'FrontEnd Develop > Project : Team Nova MJ Search' 카테고리의 다른 글
React Router Link 클릭 시 기본 이동 동작 제어하기 (0) | 2025.08.12 |
---|---|
Zustand 함부로 가져오지 않는 것에 대하여 (3) | 2025.08.11 |
백엔드에서 요구한 데이터 구조에 맞춘 API 타입 설계 (3) | 2025.07.06 |
MJS_V2 : Atomic Design + Barrel Pattern 기반 Button 컴포넌트 구성 전략 (1) | 2025.06.20 |
복잡한 컴포넌트의 관심사 분리 (0) | 2025.03.28 |