서버로부터 전체 데이터와 한 페이지에 보여줄 데이터를 각각 불러온다.
전체 데이터를 불러오는 이유는 전체 데이터의 개수(length)를 구하여 총 몇 페이지가 필요한지 계산하기 위함이다.
그리고 Pagination 컴포넌트로 전체 페이지의 길이(allService.length), 페이지 번호(page), 페이지 당 보여줄 데이터 개수(limit)을 props로 넘겨준다.
import axios from "axios";
import ServiceItem from "@/components/service/manage/ServiceItem";
import Pagination from "@/components/common/Pagination";
// 전체 데이터
const getAllService = async () => {
const res = await axios.get(`http://localhost:3001/service`);
const data = await res.data;
return data;
};
// 한 페이지에 보여줄 데이터
const getService = async (page: any, limit: any) => {
const res = await axios.get(`http://localhost:3001/service?_page=${page}&_limit=${limit}`);
const data = await res.data;
return data;
};
export default async function Manage({ searchParams: { page, limit } }: any) {
const allService = await getAllService();
const service = await getService(page, limit);
return (
<div className='flex flex-col gap-y-[40px]'>
<ul className='border-y border-solid border-[#DFDFDF]'>
{service.map((data: any) => {
return <ServiceItem key={data.id} data={data} />;
})}
</ul>
// 전체 페이지의 길이(allService.length), 페이지 번호(page), 페이지 당 보여줄 데이터 개수(limit)을 props로 전달
<Pagination allServiceLength={allService.length} page={page} limit={limit} />
</div>
);
}
위는 Next js server component 환경에서 개발한 코드라 searchParams props로 page와 limit의 쿼리 스트링 값을 받아왔지만 useSearchParams hook을 활용하여 구할 수도 있다.
import { useSearchParams } from 'react-router-dom'; const [serchParams] = useSearchParams(); const page = searchParams.get('_page'); const limit = searchParams.get('_limit');
props로 전달받은 전체 페이지 개수(allService.length)를 페이지 당 보여줄 데이터 개수(limit)로 나누어 총 몇 페이지(totalPage)가 필요한지 계산한다.
generatePaginationNumbers 함수로 한 페이지 블록에 몇 개의 페이지를 보여줄지 계산하여 페이지 번호가 담긴 배열을 반환한다. 아래 코드는 현재 페이지 좌우로 각각 2개의 페이지를 더 보여주어 1부터 5까지의 페이지를 보여주며 6페이지로 이동 시 6페이지부터 10페이지를 보여준다.
아래 gif의 왼쪽 하단 터미널을 보면 버튼을 클릭할 때마다 페이지 번호가 담긴 배열이 생성되는 것을 확인할 수 있다. 그 후 generatePaginationNumbers 함수로 생성된 generatedNumbers를 map으로 뿌려주면 각 페이지 버튼들이 생성된다.
import Link from "next/link";
import ChevronLeftIcon from "@/assets/icons/ChevronLeftIcon";
import ChevronRightIcon from "@/assets/icons/ChevronRightIcon";
interface PaginationProps {
allServiceLength: number;
page: string;
limit: string;
}
export default function Pagination({ allServiceLength, page, limit }: PaginationProps) {
// 전체 페이지 개수
let totalPage = Math.ceil(allServiceLength / Number(limit));
// 페이지 블록 생성
let generatePaginationNumbers = (page: string, totalPage: number) => {
let paginationArray = [];
// 현재 페이지 좌우로 2개의 버튼만 보이도록 설정
for (let i = Number(page) - 2; i <= Number(page) + 2; i++) {
if (i < 1) continue;
if (i > totalPage) break;
paginationArray.push(i);
}
return paginationArray;
};
// 각 페이지 버튼이 담긴 배열
let generatedNumbers = generatePaginationNumbers(page, totalPage);
return (
<div className='flex justify-between items-center px-[20px]'>
<div className='text-[14px] text-[#6C757D]'>{allServiceLength}</div>
<div className='flex items-center gap-x-[10px]'>
{Number(page) - 1 >= 1 && (
<Link
href={`/service/manage?page=${Number(page) - 1}&limit=${limit}`}
className='flex justify-center items-center w-[25px] h-[30px] bg-[#DFDFDF] rounded-sm'
>
<ChevronLeftIcon />
</Link>
)}
// 각 페이지 이동 버튼
{generatedNumbers.map((number) => (
<Link
key={number}
className={`${
Number(page) === number ? "bg-primary text-white" : "text-[#6C757D]"
} flex justify-center items-center w-[25px] h-[30px] text-[14px] rounded-sm`}
href={`/service/manage?page=${number}&limit=${limit}`}
>
{number}
</Link>
))}
{Number(page) + 1 <= totalPage && (
<Link
href={`/service/manage?page=${Number(page) + 1}&limit=${limit}`}
className='flex justify-center items-center w-[25px] h-[30px] bg-[#DFDFDF] rounded-sm'
>
<ChevronRightIcon />
</Link>
)}
</div>
</div>
);
}
{generatedNumbers.map((number) => (
<Link
key={number}
className={`${
Number(page) === number ? "bg-primary text-white" : "text-[#6C757D]"
} flex justify-center items-center w-[25px] h-[30px] text-[14px] rounded-sm`}
href={`/service/manage?page=${number}&limit=${limit}`}
>
{number}
</Link>
))}