목표
- 리액트 쿼리를 사용해 재사용성이 높은 페이지네이션 훅을 만든다
- 한 페이지에 보여줄 데이터 개수를 정할 수 있다
목차
1. 초기 설정
2. 데이터 불러오기 (렌더링하기)
3. 무한스크롤 구현하기
4. 전체 코드
[1. 초기설정]
0. 예시로 댓글을 렌더링하는 코드를 구현한다
1. 서버 코드
- 여기에서 확인할 수 있다
2. 데이터 타입
// src/types/comment.type.ts
export type DComment = {
id: number;
nickname: string;
content: string;
};
3. api연결
- getComment
- 페이지값(page)과 한 페이지에 보여줄 댓글 개수(limit)를 통해 댓글을 조건에 맞게 가져온다
- 반환 타입은 앞서 설정한 DComment의 배열 형태이다
// src/api/comment/comment.api.ts
import { DComment } from "@/types/comment.type";
import { server } from "..";
export const getComment = async (
page: number,
limit: number
): Promise<DComment[]> => {
const commentList = await server.get("comment", {
params: { page, limit },
});
return commentList.data;
};
[2. 데이터 불러오기 (렌더링하기)]
1. useQueryDetDataPerPAge
- 인자로 데이터를 가져오는 함수(fetchData)와 한 페이지에 보여줄 데이터의 수(limit)을 받는다
- useInfiniteQuery를 사용한다
- 현재 페이지(currentPage)에 포함된 데이터의 개수가 limit보다 작으면 마지막 페이지인 것이므로 undefined를 리턴
- 초기 페이지값은 1로 설정
// src/hooks/pagination/useQuery.getData.tsx
import { useInfiniteQuery } from "@tanstack/react-query";
interface PaginationProps {
fetchData: ({ pageParam }: { pageParam?: number }) => Promise<any>;
limit: number;
}
export default function useQueryGetDataPerPage({
fetchData,
limit,
}: PaginationProps) {
return useInfiniteQuery({
queryKey: ["data"],
queryFn: fetchData,
getNextPageParam: (currentPage, allPages) => {
if (currentPage.length < limit) return undefined;
return allPages.length + 1;
},
initialPageParam: 1,
});
}
2. 데이터 렌더링하기
(1) fetchData 함수
- pageParam을 매개변수로 받아 LIMIT_PER_PAGE 값을 사용하여 getComment 함수 호출
- getComment 함수는 주어진 페이지 번호와 페이지당 항목 수를 기반으로 댓글 데이터를 반환
(2) useQueryGetDataPerPage 훅:
- fetchData 함수와 LIMIT_PER_PAGE를 인자로 전달하여 댓글 데이터를 페이지 단위로 가져옴
- data, fetchNextPage, hasNextPage, isFetchingNextPage 등의 값을 반환합니다.
- data : 렌더링하고자 하는 데이터
- fetchNextPage : 다음 페이지(limit개의 데이터) 가져오기
- hasNextPage : 남은 데이터가 있는지 확인
- isFetchingNextPage : 다음 데이터 불러오는 중
(3) 데이터 렌더링
- map과 page를 사용하여 하나씩 렌더링한다
"use client";
import { getComment } from "@/api/comment/comment.api";
import useQueryGetDataPerPage from "@/hooks/comment/useQuery.getComments";
import { DComment } from "@/types/comment.type";
const LIMIT_PER_PAGE = 7;
export default function Home() {
const fetchData = async ({ pageParam = 1 }) => {
return await getComment(pageParam, LIMIT_PER_PAGE);
};
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useQueryGetDataPerPage({ fetchData, limit: LIMIT_PER_PAGE });
return (
<div>
<div>Comments</div>
{data?.pages.flatMap((page) =>
page.map((comment: DComment, index: number) => (
<div key={index}>
<p>{comment.id}</p>
<p>닉네임 : {comment.nickname}</p>
<p>내용 : {comment.content}</p>
</div>
))
)}
</div>
);
}
- 아래와 같이 나타난다 (아직 무한스크롤 기능을 넣지 않아 limit개의 데이터만 가져옴)
[3. 무한스크롤 구현하기]
1. Intersection Observer
- useEffect 훅 내에서 IntersectionObserver를 설정하여 loadMoreRef가 화면에 나타날 때마다 fetchNextPage 함수를 호출
- threshold 값을 1.0으로 설정하여 요소가 100% 화면에 나타날 때 트리거
2. 댓글 목록 렌더링
- data?.pages.flatMap을 사용하여 페이지 데이터를 평탄화(flatten), 각 댓글을 렌더링
- DComment 타입을 사용하여 각 댓글의 id, nickname, content를 표시
3. 무한 스크롤 로딩 표시
- loadMoreRef가 참조하는 div 요소에 isFetchingNextPage가 true일 때 "Loading..." 텍스트를 표시
// src/app/page.tsx
import { useEffect, useRef } from "react";
...
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useQueryGetDataPerPage({ fetchData, limit: LIMIT_PER_PAGE });
const loadMoreRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasNextPage) {
fetchNextPage();
}
},
{ threshold: 1.0 }
);
if (loadMoreRef.current) {
observer.observe(loadMoreRef.current);
}
return () => {
observer.disconnect();
};
}, [hasNextPage, fetchNextPage]);
return (
<div>
...
<div ref={loadMoreRef}>{isFetchingNextPage ? "Loading..." : ""}</div>
</div>
);
}
- 아래와 같이 구현된다
[4. 전체 코드]
1. useQuery.getData.tsx
import { useInfiniteQuery } from "@tanstack/react-query";
interface PaginationProps {
fetchData: ({ pageParam }: { pageParam?: number }) => Promise<any>;
limit: number;
}
export default function useQueryGetDataPerPage({
fetchData,
limit,
}: PaginationProps) {
return useInfiniteQuery({
queryKey: ["data"],
queryFn: fetchData,
getNextPageParam: (currentPage, allPages) => {
if (currentPage.length < limit) return undefined;
return allPages.length + 1;
},
initialPageParam: 1,
});
}
2. page.tsx
"use client";
import { getComment } from "@/api/comment/comment.api";
import useQueryGetDataPerPage from "@/hooks/comment/useQuery.getComments";
import { DComment } from "@/types/comment.type";
import { useEffect, useRef } from "react";
const LIMIT_PER_PAGE = 7;
export default function Home() {
const fetchData = async ({ pageParam = 1 }) => {
return await getComment(pageParam, LIMIT_PER_PAGE);
};
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useQueryGetDataPerPage({ fetchData, limit: LIMIT_PER_PAGE });
const loadMoreRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasNextPage) {
fetchNextPage();
}
},
{ threshold: 1.0 }
);
if (loadMoreRef.current) {
observer.observe(loadMoreRef.current);
}
return () => {
observer.disconnect();
};
}, [hasNextPage, fetchNextPage]);
return (
<div>
<div>Comments</div>
{data?.pages.flatMap((page) =>
page.map((comment: DComment, index: number) => (
<div key={index}>
<p>{comment.id}</p>
<p>닉네임 : {comment.nickname}</p>
<p>내용 : {comment.content}</p>
</div>
))
)}
<div ref={loadMoreRef}>{isFetchingNextPage ? "Loading..." : ""}</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 |