React Query simplifies infinite scroll with built-in caching.
Code:
1import { useInfiniteQuery } from "@tanstack/react-query";2import { useRef, useCallback } from "react";34function fetchPosts({ pageParam = 1 }) {5 return fetch(`/api/posts?page=${pageParam}`).then(res => res.json());6}78function InfinitePosts() {9 const {10 data,11 fetchNextPage,12 hasNextPage,13 isFetchingNextPage,14 isLoading,15 } = useInfiniteQuery({16 queryKey: ["posts"],17 queryFn: fetchPosts,18 initialPageParam: 1,19 getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,20 });2122 const observerRef = useRef<IntersectionObserver>();23 const lastPostRef = useCallback((node: HTMLDivElement | null) => {24 if (isFetchingNextPage) return;25 if (observerRef.current) observerRef.current.disconnect();2627 observerRef.current = new IntersectionObserver(entries => {28 if (entries[0].isIntersecting && hasNextPage) {29 fetchNextPage();30 }31 });3233 if (node) observerRef.current.observe(node);34 }, [isFetchingNextPage, hasNextPage, fetchNextPage]);3536 if (isLoading) return <div>Loading...</div>;3738 return (39 <div>40 {data?.pages.map((page, pageIndex) => (41 <div key={pageIndex}>42 {page.posts.map((post, postIndex) => {43 const isLast =44 pageIndex === data.pages.length - 1 &&45 postIndex === page.posts.length - 1;46 return (47 <div48 key={post.id}49 ref={isLast ? lastPostRef : null}50 >51 {post.title}52 </div>53 );54 })}55 </div>56 ))}57 {isFetchingNextPage && <div>Loading more...</div>}58 {!hasNextPage && <div>No more posts</div>}59 </div>60 );61}
Benefits: