August 04, 2022
react-query를 사용해서 서버로부터 가져온 데이터를 관리할 때 가장 흔하게 useQuery가 사용된다.
그런데 useQuery 같은 경우에는 고유한 query-key에 해당하는 데이터가 만료되면 새로운 데이터를 패칭해서 기존의 데이터를 덮어버리는 방식이기에 무한 스크롤 기능처럼 기존의 데이터는 그대로 두고 새로운 데이터를 가져와야 할 때 사용하기에는 어려움이 있는데 이런 경우에 useInfinityQuery를 사용하면 좋다.
useInfinityQuery의 결과에 담긴 data
는 useQuery와 다르게 data.pages
와 data.pageParams
가 존재하는데, 다음 페이지나 이전 페이지의 데이터를 가져오면 기존의 data.pages
에 새로운 데이터가 맨 앞이나 맨 뒤에 덧붙혀지는 방식으로 작동한다.
const {
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
...result
} = useInfiniteQuery(queryKey, ({ pageParam = 1 }) => fetchPage(pageParam), {
...options,
getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
})
pageParam
은 서버에 요청할 때 어떤 페이지의 데이터를 가져올 지 표현할 수 있으며 시작 페이지를 줄 수 있다.pageParam
값을 반환하면 된다.undefined
를 반환하면 된다.pageParam
값을 반환하면 된다.undefined
를 반환하면 된다.pageParam
값이 들어있다import React, { Fragment, useEffect } from 'react';
import { useInfiniteQuery } from 'react-query';
import { useInView } from 'react-intersection-observer';
import { AxiosError } from 'axios';
import PhotoCard from './PhotoCard';
import SkeletonPhotoCard from './SkeletonPhotoCard';
interface PhotoListProps {
children?: React.ReactNode;
}
const PhotoListDefaultProps = {};
const fetchData = (limit: number, pageParam: number) => {
const url = `/api/photo`;
const params = { limit, pageParam }
const res = await client.get(url, { params });
return res.data;
}
function PhotoList({ children }: PhotoListProps & typeof PhotoListDefaultProps) {
const limit = 20; // 한 페이지에 보여줄 아이템 갯수
const [viewRef, inView] = useInView();
// 데이터 가져오기
const { data: photos, error, isFetching, fetchNextPage, hasNextPage } =
useInfiniteQuery(['photos'], ({ pageParam = 0 }) => fetchData(limit, pageParam),
{
getNextPageParam: (lastPage, pages) => {
return lastPage?.photos.length === limit && lastPage.pageParam + limit;
}
});
// 다음 페이지 가져오기
useEffect(() => {
if (!inView) return;
if (!photos) return;
if (!hasNextPage) return;
fetchNextPage();
console.log(inView);
}, [inView]);
return (
<>
<section className="photo-section">
{photos?.pages.map((page, pageIdx) =>
<Fragment key={pageIdx}>
{page?.photos.map((item) => (
<PhotoCard key={item.photocard_id} photo={item} />
))}
</Fragment>
)}
{isFetching && Array.from({length: limit}).map((_, idx) => (
<SkeletonPhotoCard key={idx} />
))}
</section>
<div ref={viewRef} />
</>
);
}
PhotoList.defaultProps = PhotoListDefaultProps;
export default PhotoList;