React-Query
는 useInfiniteQuery
hook을 제공합니다.
문서에도 해당 hook에 대한 API 명세와 사용 방법이 소개되고 있는데요.
공식 문서의 설명이 조금 부족해서 그런지는 모르겠는데, 이해가 되지 않는 부분이 있었고 삽질도 하였습니다.
페이지네이션을 구현한 코드를 정리하였습니다.
비동기 통신 라이브러리로는 axios
를 사용하였습니다.
// 페이징을 구현한 컴포넌트
import axios from "axios";
import { useInfiniteQuery } from "react-query";
interface PostData {
body: string;
id: number;
title: string;
userId: number;
}
function Test1() {
const API_URL = "https://jsonplaceholder.typicode.com";
const fetchPost = (param: any) => {
const start = param.pageParam?.start || 0;
const limit = param.pageParam?.limit || 5;
return axios
.get<PostData[]>(`${API_URL}/posts?_start=${start}&_limit=${limit}`)
.then(({ data }) => data);
};
const { data, fetchNextPage } = useInfiniteQuery({
queryKey: "getPosts",
queryFn: fetchPost,
getNextPageParam: (lastPage) => {
const lastIdx = lastPage.length - 1;
const lastPost = lastPage[lastIdx];
return lastPost.id < 100
? {
start: lastPost.id + 1,
limit: 5,
}
: false;
},
});
return (
<div>
{data?.pages.map((page) => {
return page.map((post) => {
const { id, title } = post;
return (
<div key={`post-${id}`} style={{ marginBottom: "50px" }}>
<p>{id}</p>
<p>{title}</p>
</div>
);
});
})}
<div>
<button onClick={() => fetchNextPage()}>Load More...</button>
</div>
</div>
);
}
export default Test1;
// useInfiniteQuery
const { data, fetchNextPage } = useInfiniteQuery({
queryKey: "getPosts",
queryFn: fetchPost,
getNextPageParam: (lastPage) => {
const lastIdx = lastPage.length - 1;
const lastPost = lastPage[lastIdx];
return lastPost.id < 100
? {
start: lastPost.id + 1,
limit: 5,
}
: false;
},
먼저 useInfiniteQuery
를 살펴보면,
useQuery
처럼 useInfiniteQuery
도 queryKey
, queryFn
등을 받습니다.
좀 더 나아가서, getNextPageParam
을 전달해줄 수 있는데요.
getNextPageParam
은 다음 페이지의 데이터를 가져올 함수에 전달할 파라미터를 결정해줄 수 있습니다.
getNextPageParam
은 lastPage
와 pages
의 두 가지 파라미터를 전달받는데요.
lastPage
는 마지막 페이지의 데이터이고, pages
는 모든 페이지의 데이터를 의미합니다.
한 페이지 데이터의 형태가 PostData
객체라고 하면,
lastPage
는 마지막 페이지의 PostData
객체를 가지고 있고,
pages
는 PostData
의 배열 형태가 됩니다. (PostData[]
)
getNextPageParam
이 falsy 값을 리턴하면, 더이상 추가 데이터를 가져오지 않습니다.
const fetchPost = (param: any) => {
const start = param.pageParam?.start || 0;
const limit = param.pageParam?.limit || 5;
return axios
.get<PostData[]>(`${API_URL}/posts?_start=${start}&_limit=${limit}`)
.then(({ data }) => data);
};
fetchPost
함수는 useInfiniteQuery
의 queryFn
에 전달한 함수입니다.
queryFn
은 QueryFunctionContext
객체를 파라미터로 전달받습니다.
이 객체 안에 pageParam
이 존재하는데, 위의 getNextPageParam
에서 리턴한 값입니다.
JSONPlaceholder API에서는 _start
와 _limit
URL 파라미터를 전달해줄 수 있는데요.
_start는 데이터의 id 시작값을, _limit는 가져올 데이터 갯수를 의미합니다.
_start 값이 0이고 _limit이 5이면,
5개의 포스트 id값은 순차적으로 0, 1, 2, 3, 4 입니다.
JSONPlaceholder API는 포스트의 id값이 오름차순 형태로 데이터를 가져올 수 있기 때문에, 페이지네이션을 구현할 수 있습니다.
다음에 불러올 id값만 넘겨주면 되는 것입니다.
return (
<div>
{data?.pages.map((page) => {
return page.map((post) => {
const { id, title } = post;
return (
<div key={`post-${id}`} style={{ marginBottom: "50px" }}>
<p>{id}</p>
<p>{title}</p>
</div>
);
});
})}
<div>
<button onClick={() => fetchNextPage()}>Load More...</button>
</div>
</div>
);
모든 페이지의 데이터를 불러와서 UI를 렌더링해주는 코드입니다.
useInfiniteQuery
의 리턴값으로 가져온 data.pages
를 이용해주면 되는데요.
pages
는 모든 페이지 데이터를 배열형태로 가지고 있습니다.
적절하게 map
을 사용해서 화면에 렌더링해주면 되겠습니다.
// pseudo 코드
// 1페이지 데이터 (PostData[])
[ PostData, PostData, PostData, ... ]
// 2페이지 데이터 (PostData[])
[ PostData, PostData, PostData, ... ]
...
// pages (PostData[][])
[ PostData[], PostData[], PostData[], ... ]
return (
<div>
{data?.pages.map((page) => {
return page.map((post) => {
const { id, title } = post;
return (
<div key={`post-${id}`} style={{ marginBottom: "50px" }}>
<p>{id}</p>
<p>{title}</p>
</div>
);
});
})}
<div>
<button onClick={() => fetchNextPage()}>Load More...</button>
</div>
</div>
);
UI 하단을 보시면, Load More... 이라는 텍스트를 가진 버튼이 존재하는데요.
버튼에 클릭 이벤트로 fetchNextPage
를 호출하도록 하였습니다.
fetchNextPage
도 useInfiniteQuery
의 리턴값으로 가져올 수 있는데요.
fetchNextPage
함수를 이용해서, 다음 페이지를 가져올 수 있을 때, 다음 페이지 데이터를 가져올 수 있습니다.
샘플
참고
https://tanstack.com/query/v4/docs/guides/infinite-queries
https://tanstack.com/query/v4/docs/examples/react/load-more-infinite-scroll
'React' 카테고리의 다른 글
[React] 컴포넌트를 어떻게 잘 구현할 수 있을까? (0) | 2023.07.29 |
---|---|
[React] 리액트 컴포넌트 최적화해보기 (with. useCallback, React.memo) (0) | 2023.06.22 |
[React] React-Query 기본 사용법 정리 (useQuery, useMutation) (0) | 2023.01.20 |
[React] React-Query 상태(Status) & StaleTime, CacheTime 정리 (0) | 2023.01.18 |
[React] React + Swiper.js를 이용해서 카드 UI를 인라인 형식으로 보여주기 (0) | 2022.07.29 |