목표
- 리액트 쿼리를 사용해 재사용성이 높은 페이지네이션 훅을 만든다
- 한 페이지에 보여줄 데이터 개수를 정할 수 있다
- 페이지 번호를 클릭하면 해당 페이지로 이동한다
- '이전', '이후' 버튼을 사용해 다른 데이터를 렌더링할 수 있다
- 마지막 페이지일 경우, 이후페이지를 클릭할 수 없고 첫 페이지일 경우 이전 페이지를 클릭할 수 없다
목차
1. 초기 설정
2. 데이터 불러오기 (렌더링하기)
3. 페이지 버튼 구현하기 - 훅 구현
4. 데이터 가져오기 및 이전페이지, 이후페이지 구현하기 - 훅 구현
5. 전체 코드
[1. 초기 설정]
0. 예시로 댓글을 렌더링하는 코드를 구현한다
1. 서버 코드
- 여기에서 확인할 수 있다
2. 데이터 타입
// src/types/comment.type.ts
export type DComment = {
id: number;
nickname: string;
content: string;
};
3. api연결
(1) getComment
- 페이지값(page)과 한 페이지에 보여줄 댓글 개수(limit)를 통해 댓글을 조건에 맞게 가져온다
- 반환 타입은 앞서 설정한 DComment의 배열 형태이다
(2) getTotalComments
- 전체 댓글의 개수를 가져온다
- 페이지 번호 버튼을 구현하는데 사용된다
// src/api/comment.api.ts
export const getComment = async (
page: number,
limit: number
): Promise<DComment[]> => {
const commentList = await server.get("comment", {
params: { page, limit },
});
return commentList.data;
};
export const getTotalComments = async (): Promise<number> => {
const totalComments = await server.get("comment/count");
return totalComments.data;
};
[2. 데이터 가져오기]
1. 데이터 가져오기
- 현재 페이지(currentPage)와 페이지당 데이터 개수(limit) 그리고 배열 pages를 인자로 전달받는다
- useQuery를 사용해 서버에서 데이터를 가져온다
// src/hooks/comment/useQuery.getComments.tsx
import { getComment } from "@/api/comment/comment.api";
import { useQuery } from "@tanstack/react-query";
export default function useQueryGetCommentsPerPage(
currentPage: number,
limit: number
) {
const { data } = useQuery({
queryKey: ["data", currentPage],
queryFn: () => getComment(currentPage, limit),
});
return {
data
};
}
2. 데이터 렌더링하기
- map을 사용해 데이터를 하나씩 보여준다
// src/app/page.tsx
import useQueryGetCommentsPerPage from "@/hooks/comment/useQuery.getComments";
...
const { data } = useQueryGetCommentsPerPage(
currentPage,
LIMIT_PER_PAGE,
);
return (
<div>
<div>Comments</div>
<div>
<strong>번호</strong>
<strong>닉네임</strong>
<strong>내용</strong>
</div>
{data?.map((comment: DComment, index: number) => (
<div key={index}>
<p>{comment.id}</p>
<p>{comment.nickname}</p>
<p>{comment.content}</p>
</div>
))}
...
</div>
);
}
- 결과는 아래와 같다
[3. 페이지 버튼 구현하기 - 훅 구현]
1. 전체 데이터의 개수 가져오기
- 한 페이지에 렌더링할 데이터 개수(limit)에 따라 페이지 버튼의 개수가 달라진다
- 버튼 개수를 page로 설정한다
- 전체 데이터 개수를 limit으로 나눴을 때 나머지가 생긴다면, 몫에 1을 더해준다
- page 크기의 배열 pages를 만들어주고, 값을 1부터 page까지 매겨준다
import { getTotalComments } from "@/api/comment/comment.api";
import { useQuery } from "@tanstack/react-query";
export default function useQueryGetTotalComments(limit: number) {
const { data } = useQuery({
queryKey: ["data"],
queryFn: () => getTotalComments(),
});
const page =
data &&
(data % limit > 0
? Math.floor(data / limit) + 1
: Math.floor(data / limit));
const pages = [page];
if (page) {
for (let i = 0; i < page; i++) {
pages[i] = i + 1;
}
}
return {
pages,
};
}
2. 화면에 페이지 버튼 구현
(1) LIMIT_PER_PAGE를 한 페이지에 렌더링할 데이터의 개수로 설정한다.
(2) currentPage 관리
- useState로 현재 페이지를 관리한다
- 초기값은 1로 설정한다
(3) onHandlePage : 페이지 설정 함수
- 페이지 버튼을 클릭하면 currentPage를 해당 페이지로 설정하는 함수를 구현한다
(4) 배열 pages를 불러오고 화면에 map으로 하나씩 구현
// src/app/page.tsx
"use client";
import { useState } from "react";
const LIMIT_PER_PAGE = 10;
export default function Home() {
const [currentPage, setCurrentPage] = useState(1);
const onHandlePage = (page: number) => {
setCurrentPage(page);
};
return (
<div>
...
<div>
{pages?.map((page: number | undefined, index: number) => (
<button
key={index}
title="page number"
type="button"
onClick={() => onHandlePage(page || 1)}
>
{page}
</button>
))}
</div>
</div>
);
}
- 아래와 같이 나타남 (데이터 개수에 따라 번호는 달라짐)
[4. 데이터 가져오기 및 이전페이지, 이후페이지 구현하기 - 훅 구현]
1. 이전/이후 페이지 구현
- 이전페이지, 이후페이지를 오갈 수 없는 조건을 설정한다
(1) isPrevDisabled
- 이전페이지가 존재하지 않는 경우
- currentPage가 1인 경우
(2) isNextDisabled
- 이후페이지가 존재하지 않는 경우
- 현재 페이지가 pages 배열의 길이와 같은 경우
// src/app/page.tsx
...
const isPrevDisabled = currentPage === 1;
const isNextDisabled = pages && currentPage === pages.length;
...
}
2. 화면에 이전, 이후 페이지 버튼 구현
(1) 이전 페이지 버튼 : onHandlePrevPage
- isPrevDisabled가 아닌 경우에 currentPage-1
(2) 이후 페이지 버튼 : onHandleNextPage
- isNextDisabled가 아닌 경우에 currentPage+1
// src/app/page.tsx
...
const onHandlePrevPage = () => {
if (!isPrevDisabled) setCurrentPage(currentPage - 1);
};
const onHandleNextPage = () => {
if (!isNextDisabled) setCurrentPage(currentPage + 1);
};
const onHandlePage = (page: number) => {
setCurrentPage(page);
};
return (
<div>
...
<div>
<button
title="prev"
type="button"
onClick={onHandlePrevPage}
>
이전
</button>
...
<button
title="next"
type="button"
onClick={onHandleNextPage}
>
이후
</button>
</div>
</div>
);
}
- 아래와 같이 나타난다
[5. 전체 코드]
1. 데이터 개수를 가져오는 코드 : useQuery.getTotalComments.tsx
import { getTotalComments } from "@/api/comment/comment.api";
import { useQuery } from "@tanstack/react-query";
export default function useQueryGetTotalComments(limit: number) {
const { data } = useQuery({
queryKey: ["data"],
queryFn: () => getTotalComments(),
});
const page =
data &&
(data % limit > 0
? Math.floor(data / limit) + 1
: Math.floor(data / limit));
const pages = [page];
if (page) {
for (let i = 0; i < page; i++) {
pages[i] = i + 1;
}
}
return {
pages,
};
}
2. 데이터를 가져오는 코드 : useQuery.getComments.tsx
import { getComment } from "@/api/comment/comment.api";
import { useQuery } from "@tanstack/react-query";
export default function useQueryGetCommentsPerPage(
currentPage: number,
limit: number
) {
const { data } = useQuery({
queryKey: ["data", currentPage],
queryFn: () => getComment(currentPage, limit),
});
return {
data,
};
}
3. 홈페이지 코드
"use client";
import useQueryGetCommentsPerPage from "@/hooks/comment/useQuery.getComments";
import useQueryGetTotalComments from "@/hooks/comment/useQuery.getTotalComments";
import { DComment } from "@/types/comment.type";
import { useState } from "react";
const LIMIT_PER_PAGE = 10;
export default function Home() {
const { pages } = useQueryGetTotalComments(LIMIT_PER_PAGE);
const [currentPage, setCurrentPage] = useState(1);
const { data } = useQueryGetCommentsPerPage(currentPage, LIMIT_PER_PAGE);
const isPrevDisabled = currentPage === 1;
const isNextDisabled = pages && currentPage === pages.length;
const onHandlePrevPage = () => {
if (!isPrevDisabled) setCurrentPage(currentPage - 1);
};
const onHandleNextPage = () => {
if (!isNextDisabled) setCurrentPage(currentPage + 1);
};
const onHandlePage = (page: number) => {
setCurrentPage(page);
};
return (
<div>
<div>Comments</div>
<div>
<p>번호</p>
<p>닉네임</p>
<p>내용</p>
</div>
{data?.map((comment: DComment, index: number) => (
<div key={index}>
<p>{comment.id}</p>
<p>{comment.nickname}</p>
<p>{comment.content}</p>
</div>
))}
<div>
<button
title="prev"
type="button"
onClick={onHandlePrevPage}>
이전
</button>
{pages?.map((page: number | undefined, index: number) => (
<button
key={index}
title="page number"
type="button"
onClick={() => onHandlePage(page || 1)}
>
{page}
</button>
))}
<button
title="next"
type="button"
onClick={onHandleNextPage}
>
이후
</button>
</div>
</div>
);
}
'Next.js' 카테고리의 다른 글
[Next.js] 페이지네이션 구현하기, 무한스크롤 (react-query) (0) | 2024.07.04 |
---|---|
[Next.js] Modal 구현하기 (0) | 2024.06.25 |
[Next.js] 이미지 업로드하기 (Nest.js연결 및 S3에 업로드) (1) | 2024.06.14 |