TanStack Query simplifies infinite scroll with built-in caching.
Code:
1import { useInfiniteQuery } from "@tanstack/react-query";2import { useRef, useCallback } from "react";3import { useIntersectionObserver } from "@tanstack/react-query";45function fetchPosts({ pageParam = 1 }) {6 return fetch(`/api/posts?page=${pageParam}`).then(res => res.json());7}89function InfinitePosts() {10 const {11 data,12 fetchNextPage,13 hasNextPage,14 isFetchingNextPage,15 isLoading,16 isError,17 } = useInfiniteQuery({18 queryKey: ["posts"],19 queryFn: fetchPosts,20 initialPageParam: 1,21 getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,22 });2324 const observerRef = useRef<IntersectionObserver>();25 const lastPostRef = useCallback((node: HTMLDivElement | null) => {26 if (isFetchingNextPage) return;27 if (observerRef.current) observerRef.current.disconnect();2829 observerRef.current = new IntersectionObserver(entries => {30 if (entries[0].isIntersecting && hasNextPage) {31 fetchNextPage();32 }33 });3435 if (node) observerRef.current.observe(node);36 }, [isFetchingNextPage, hasNextPage, fetchNextPage]);3738 if (isLoading) return <div>Loading...</div>;39 if (isError) return <div>Error loading posts</div>;4041 return (42 <div>43 {data?.pages.map((page, pageIndex) => (44 <div key={pageIndex}>45 {page.posts.map((post, postIndex) => {46 const isLast =47 pageIndex === data.pages.length - 1 &&48 postIndex === page.posts.length - 1;49 return (50 <div51 key={post.id}52 ref={isLast ? lastPostRef : null}53 className="post"54 >55 <h3>{post.title}</h3>56 <p>{post.body}</p>57 </div>58 );59 })}60 </div>61 ))}6263 {isFetchingNextPage && <div>Loading more...</div>}64 {!hasNextPage && <div>No more posts</div>}65 </div>66 );67}
Benefits: