사용자가 눈으로 보고 있는 화면의 높이(clientHeight)와 사용자가 스크롤해서 내린만큼의 높이(scrollTop)를 더한 값이, 전체화면에 대한 높이(scrollHeight)와 같으면 새로운 데이터를 받아오는 방식
window.addEventListner("scroll", infiniteScroll);
fetch(`${API}/posts?offset=${offset}&limit=${limit}`)
.then(res => res.json())
.then(res => {
setState({
products: res.products,
offset: offset + limit,
});
});
const infiniteScroll = () => {
const scrollHeight = Math.max(
document.documentElement.scrollHeight,
document.body.scrollHeight
);
const scrollTop = Math.max(
document.documentElement.scrollTop,
document.body.scrollTop
);
const clientHeight = document.documentElement.clientHeight;
if (scrollTop + clientHeight === scrollHeight) {
fetch(`${API}/posts?offset=${offset}&limit=${limit}`)
.then(res => res.json)
.then(res => {
setState({
products: [...products, ...res.products],
offset: offset + limit,
});
setState({ isLoading: true});
setTimeout(() => {
setState({ isLoading: false });
}, 350);
});
}
타겟 요소와 타겟의 부모 혹은 상위 요소의 뷰포트가 교차되는 부분을 비동기적으로 관찰하는 API, IntersectionObserver를 활용하여 화면에 지정한 타겟 요소가 보이면 데이터를 불러옴
function ProductPage () {
const [ target, setTarget ] = useState(null);
// ...
const _fetchProductItems = () => {
const productItems = apiProductItems(itemLength);
if (!productItems.length) {
actions.isLoaded(dispatch)(false);
return;
}
// ...
};
useEffect(() => {
let observer;
if (target) {
observer = new IntersectionObserver(_onIntersect, { threshold: 1 });
observer.observe(target);
}
return () => observer && observer.disconnect();
}, [ target ]);
const _onIntersect = ([ entry ]) => {
if (entry.isIntersecting) {
_fetchProductItems();
}
};
// ...
return (
<>
// ...
{state.isLoaded && <div ref={setTarget}>loading</div>}
</>
);
}
export default ProductPage;
tanstack-query의 useInfiniteQuery를 활용한 무한 스크롤
const {
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
...result
} = useInfiniteQuery({
queryKey,
queryFn: ({ pageParam }) => fetchPage(pageParam),
initialPageParam: 1,
...options,
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
lastPage.nextCursor,
getPreviousPageParam: (firstPage, allPages, firstPageParam, allPageParams) =>
firstPage.prevCursor,
})
import PostItem from '../../../components/common/PostItem';
import styles from '../styles/Main.News.module.scss';
import {
QueryFunction,
QueryFunctionContext,
useInfiniteQuery,
} from '@tanstack/react-query';
import { instance } from '@/api/instance';
interface PostsDataType {
pages: {
totalPage: number;
currentPage: number;
posts: {
id: number;
title: string;
content: string;
createdAt: string;
updatedAt: string;
authorId: number;
viewCount: number;
postImg: null | string;
}[];
}[];
}
export interface IRepository {
total_count: number;
incomplete_results: boolean;
items: PostsDataType[];
nextCursor?: string;
prevCursor?: string;
}
type QueryKey = [string, string, string];
const News = () => {
const page = 1;
const fetchRepositories: QueryFunction<
IRepository,
QueryKey,
unknown
> = async ({ queryKey }: QueryFunctionContext<QueryKey>) => {
const [, page, search] = queryKey;
return await instance
.get<IRepository>(
`/post/list?page=${page}&limit=7${search && `&search=${search}`}`,
)
.then((res) => res.data);
};
const {
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
...result
} = useInfiniteQuery<IRepository, Error, PostsDataType>({
queryKey: ['postList', page.toString(), 'all'],
queryFn: fetchRepositories,
getNextPageParam: (lastPage) => lastPage.nextCursor,
getPreviousPageParam: (firstPage) => firstPage.prevCursor,
});
console.log(result?.data);
console.log(
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
);
return (
<div className={styles.container}>
{result?.data?.pages[0].posts.map((item) => (
<PostItem key={item.id} data={item} />
))}
</div>
);
};
export default News;
데이터는 불러와지는데 페이지 이동은 어떻게 하는거지..
데이터가 없으니까 된건지 확인할수가없다,,