import React, { useEffect, useRef } from 'react';
import { useInfiniteQuery, QueryFunctionContext } from '@tanstack/react-query';
interface Photo {
id: number;
title: string;
url: string;
thumbnailUrl: string;
}
const fetchPhotos = async ({ pageParam = 1 }: QueryFunctionContext): Promise<Photo[]> => {
const res = await fetch(`https://jsonplaceholder.typicode.com/photos?_page=${pageParam}&_limit=10`);
return res.json();
};
const InfiniteScroll: React.FC = () => {
const { data, error, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery<Photo[], Error>({
queryKey: ['photos'],
queryFn: fetchPhotos,
getNextPageParam: (lastPage, allPages) => lastPage.length ? allPages.length + 1 : undefined,
});
const observerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasNextPage) {
fetchNextPage();
}
});
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => {
if (observerRef.current) {
observer.unobserve(observerRef.current);
}
};
}, [fetchNextPage, hasNextPage]);
if (error) return <div>Failed to load</div>;
return (
<div>
<h1>Infinite Scroll</h1>
<div>
{data?.pages.map((page, i) => (
<React.Fragment key={i}>
{page.map((photo: Photo) => (
<div key={photo.id}>
<img src={photo.thumbnailUrl} alt={photo.title} />
<p>{photo.title}</p>
</div>
))}
</React.Fragment>
))}
</div>
{isFetchingNextPage && <p>Loading more...</p>}
<div ref={observerRef} style={{ height: '1px' }}></div>
</div>
);
};
export default InfiniteScroll;
- 장점
- 내장된 페이지네이션 처리로 무한 스크롤 구현이 간단.
getNextPageParam
으로 다음 페이지 매개변수 설정.isFetchingNextPage
상태로 로딩 상태 관리 가능.
- 단점
import React, { useEffect, useRef, useState } from 'react';
import { useQuery } from 'react-query';
interface Photo {
id: number;
title: string;
url: string;
thumbnailUrl: string;
}
const fetchPhotos = async (page: number): Promise<Photo[]> => {
const res = await fetch(`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`);
return res.json();
};
const InfiniteScroll: React.FC = () => {
const [page, setPage] = useState<number>(1);
const [photos, setPhotos] = useState<Photo[]>([]);
const observerRef = useRef<HTMLDivElement | null>(null);
const { data, error, isLoading } = useQuery(['photos', page], () => fetchPhotos(page), { keepPreviousData: true });
useEffect(() => {
if (data) {
setPhotos((prevPhotos) => [...prevPhotos, ...data]);
}
}, [data]);
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
setPage((prevPage) => prevPage + 1);
}
});
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => {
if (observerRef.current) {
observer.unobserve(observerRef.current);
}
};
}, []);
if (error) return <div>Failed to load</div>;
return (
<div>
<h1>Infinite Scroll</h1>
<div>
{photos.map((photo) => (
<div key={photo.id}>
<img src={photo.thumbnailUrl} alt={photo.title} />
<p>{photo.title}</p>
</div>
))}
</div>
{isLoading && <p>Loading...</p>}
<div ref={observerRef} style={{ height: '1px' }}></div>
</div>
);
};
export default InfiniteScroll;
- 장점
- 간단한 페이지네이션 로직 구현 가능.
- 상태를
useState
로 관리하여 코드 가독성 높음.
- 단점
useInfiniteQuery
에 비해 많은 코드 작성 필요.- 무한 스크롤 로직 수동 처리.
import React, { useEffect, useRef, useState } from 'react';
import useSWR from 'swr';
interface Photo {
id: number;
title: string;
url: string;
thumbnailUrl: string;
}
const fetcher = (url: string) => fetch(url).then((res) => res.json());
const InfiniteScroll: React.FC = () => {
const [page, setPage] = useState<number>(1);
const [photos, setPhotos] = useState<Photo[]>([]);
const observerRef = useRef<HTMLDivElement | null>(null);
const { data, error } = useSWR<Photo[]>(`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`, fetcher);
useEffect(() => {
if (data) {
setPhotos((prevPhotos) => [...prevPhotos, ...data]);
}
}, [data]);
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
setPage((prevPage) => prevPage + 1);
}
});
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => {
if (observerRef.current) {
observer.unobserve(observerRef.current);
}
};
}, []);
if (error) return <div>Failed to load</div>;
return (
<div>
<h1>Infinite Scroll</h1>
<div>
{photos.map((photo) => (
<div key={photo.id}>
<img src={photo.thumbnailUrl} alt={photo.title} />
<p>{photo.title}</p>
</div>
))}
</div>
<div ref={observerRef} style={{ height: '1px' }}></div>
</div>
);
};
export default InfiniteScroll;
- 장점
- 자동 데이터 캐싱 및 가져오기 관리.
- 간단한 설정으로 구현 가능.
- 단점
- 추가 로직 필요.
react-query
보다 명확한 내장 옵션 부족