https://tanstack.com/query/v4/docs/framework/react/reference/useInfiniteQuery
useInfiniteQuery | TanStack Query React Docs
This ad helps to keep us from burning out and rage-quitting OSS just *that* much more, so chill. 😉
tanstack.com
useInfiniteQuery는 React Query에서 제공하는 훅 중 하나로, 페이징된 데이터를 손쉽게 가져오고 관리할 수 있게 해줍니다.
일반적인 useQuery와 유사하지만, 여러 페이지의 데이터를 관리할 수 있는 추가 기능을 제공합니다.
이를 활용하면 무한 스크롤과 같은 기능을 쉽게 구현할 수 있습니다.
1. 기본 사용법
useInfiniteQuery를 사용하면 데이터를 페이지 단위로 나누어 불러오고, 사용자가 요청할 때마다 새로운 페이지의 데이터를 가져올 수 있습니다.
주요 옵션
- queryFn: 데이터를 가져오는 함수로, 반드시 Promise를 반환해야 합니다. 이 함수는 pageParam이라는 인자를 받아 특정 페이지의 데이터를 가져옵니다.
- getNextPageParam: 다음 페이지의 파라미터를 결정하는 함수입니다. lastPage와 allPages를 인자로 받아 다음 페이지의 파라미터를 반환합니다.
- getPreviousPageParam: 이전 페이지의 파라미터를 결정하는 함수입니다. firstPage와 allPages를 인자로 받아 이전 페이지의 파라미터를 반환합니다.
반환 값
- data.pages: 불러온 모든 페이지의 데이터 배열입니다.
- fetchNextPage: 다음 페이지를 가져오는 함수입니다.
- fetchPreviousPage: 이전 페이지를 가져오는 함수입니다.
- hasNextPage: 다음 페이지가 있는지 여부를 나타내는 boolean 값입니다.
- hasPreviousPage: 이전 페이지가 있는지 여부를 나타내는 boolean 값입니다.
- isFetchingNextPage: 다음 페이지를 가져오는 중인지 여부를 나타내는 boolean 값입니다.
- isFetchingPreviousPage: 이전 페이지를 가져오는 중인지 여부를 나타내는 boolean 값입니다.
2. 예제 코드
현재는 JSON 데이터를 이용해 pageSize와 같은 페이지 처리를 프론트엔드에서 구현했습니다.
모든 데이터를 한 번에 클라이언트로 가져온 후, 프론트엔드에서 페이지 단위로 슬라이스하여 표시하는 방식입니다.
실제 환경에서는 서버가 페이지 번호와 pageSize를 받아 필요한 데이터만 응답으로 보내는 것이 일반적입니다.
이 예제는 프론트엔드에서 페이지네이션의 기본 개념과 React Query의 useInfiniteQuery 훅 사용법을 익히기 위한 것입니다.
실제 데이터를 사용한 무한 스크롤 예제는 현재 진행 중인 개인 유튜브 클론 프로젝트에서 YouTube Data API를 활용해 구현할 예정입니다.
import React from "react";
import { useInfiniteQuery } from "@tanstack/react-query";
// 현재 페이지 번호를 기반으로 데이터를 가져오는 비동기 함수
const fetchVideos = async ({ pageParam = 1 }) => {
const response = await fetch('/data/videos.json');
if (!response.ok) {
throw new Error('네트워크 오류');
}
// 전체 데이터
const allData = await response.json();
// pageSize -> 한 페이지에 표시할 데이터의 수
// start, end -> 데이터의 시작 인덱스와 끝 인덱스를 계산
const pageSize = 5;
const start = (pageParam - 1) * pageSize;
const end = start + pageSize;
const videoData = allData.flatMap(page => page.videos);
// 현재 페이지에 해당하는 데이터를 잘라냄
const pageData = videoData.slice(start, end);
// nextCursor -> 다음 페이지가 있는지 여부
const nextCursor = end < videoData.length ? pageParam + 1 : null;
// 현재 페이지에 해당하는 데이터와 다음 페이지의 커서를 반환
return {
videos: pageData,
nextCursor,
};
};
const VideoList = () => {
const {
data, // 페칭된 데이터
fetchNextPage, // 다음 페이지를 가져오는 함수
hasNextPage, // 다음 페이지가 있는지 여부
isFetchingNextPage, // 다음 페이지를 가져오는 중인지 여부
isLoading, // 초기 데이터 로딩 중인지 여부
isError, // 에러 발생 여부
error, // 발생한 에러 객체
} = useInfiniteQuery({
queryKey: ['videos'],
queryFn: fetchVideos,
// lastPage는 직전의 로드된 페이지 데이터
// 다음 페이지의 파라미터를 반환하여, 다음 데이터를 로드할 수 있도록 돕는 함수
getNextPageParam: (lastPage) => lastPage.nextCursor,
});
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return <div>Error: {error.message}</div>;
}
return (
<div>
{data.pages.map((page, pageIndex) => (
<div key={pageIndex}>
{page.videos.map((video, videoIndex) => (
<div key={video.id || videoIndex} style={{ marginBottom: '20px' }}>
<h3>{video.title}</h3>
<p>{video.description}</p>
<p><strong>업로드 날짜:</strong> {video.uploadDate}</p>
</div>
))}
</div>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage ? '비디오 로딩 중...' : hasNextPage ? '더 많은 비디오 로드' : '더 이상 비디오 없음'}
</button>
</div>
);
};
export default VideoList;
아래는 예제에서 사용한 json 데이터 입니다.
[
{
"videos": [
{
"id": 1,
"title": "HTML 소개",
"description": "HTML 기본 개념",
"uploadDate": "2024-08-30"
},
{
"id": 2,
"title": "CSS 기초",
"description": "CSS 기본 사용법",
"uploadDate": "2024-08-25"
},
{
"id": 3,
"title": "JavaScript 기초",
"description": "JavaScript 기본 개념",
"uploadDate": "2024-08-20"
},
{
"id": 4,
"title": "React 소개",
"description": "React 개요",
"uploadDate": "2024-08-15"
},
{
"id": 5,
"title": "useState & useEffect",
"description": "React 훅 사용법",
"uploadDate": "2024-08-10"
}
],
"nextCursor": 2
},
{
"videos": [
{
"id": 6,
"title": "Flexbox",
"description": "CSS Flexbox 레이아웃",
"uploadDate": "2024-08-05"
},
{
"id": 7,
"title": "Grid",
"description": "CSS Grid 레이아웃",
"uploadDate": "2024-08-01"
},
{
"id": 8,
"title": "JavaScript ES6+",
"description": "최신 JavaScript 문법",
"uploadDate": "2024-07-28"
},
{
"id": 9,
"title": "React 컴포넌트",
"description": "React 컴포넌트 사용법",
"uploadDate": "2024-07-24"
},
{
"id": 10,
"title": "useReducer",
"description": "복잡한 상태 관리",
"uploadDate": "2024-07-20"
}
],
"nextCursor": 3
},
{
"videos": [
{
"id": 11,
"title": "HTML5 기능",
"description": "HTML5 새로운 기능",
"uploadDate": "2024-07-15"
},
{
"id": 12,
"title": "CSS 애니메이션",
"description": "CSS 애니메이션 사용법",
"uploadDate": "2024-07-10"
},
{
"id": 13,
"title": "비동기 처리",
"description": "JavaScript 비동기 처리",
"uploadDate": "2024-07-05"
},
{
"id": 14,
"title": "Context API",
"description": "React 전역 상태 관리",
"uploadDate": "2024-07-01"
},
{
"id": 15,
"title": "React Router",
"description": "React 라우팅",
"uploadDate": "2024-06-25"
}
],
"nextCursor": null
}
]
'React > 정리' 카테고리의 다른 글
URL 기반 모달 라우팅 구현 (0) | 2025.05.23 |
---|---|
Zustand 리렌더링 최적화 하기 + Redux Toolkit 최적화 (0) | 2025.05.18 |
useRef로 DOM 요소 참조 및 관리하기 (1) | 2024.08.31 |
React Query를 통한 데이터 관리 (0) | 2024.08.22 |
React Router(CSR) 활용법 (0) | 2024.08.21 |