πŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
πŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Infinite Queries: Paginated and Infinite-Scroll Data

Learn TanStack Query's useInfiniteQuery for paginated data: getNextPageParam, fetchNextPage, hasNextPage, and infinite scroll.

⚑ Total XP: 0|πŸ’» react XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Infinite query fundamentals.

Quick Quiz //

What does useInfiniteQuery do differently from useQuery?


πŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
πŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

Feeds, search results, and comment threads all share a pattern: accumulating pages of data as a user scrolls, rather than replacing what's already loaded. This lesson covers useInfiniteQuery, getNextPageParam, fetchNextPage, and wiring it up to true infinite scroll with an IntersectionObserver.

1Beyond a Single Page of Results

Feeds, search results, and comment threads share an accumulating pagination pattern: load the first page, then append additional pages as the user scrolls, rather than replacing existing data. useInfiniteQuery is purpose-built for this shape, distinct from useQuery's single fetch-per-key model.

2The getNextPageParam Function

useInfiniteQuery needs a getNextPageParam function that receives the most recently fetched page and returns whatever parameter (a cursor, a page number) should be passed to queryFn for the next request, or undefined to signal there are no more pages.

3Fetching the Next Page with fetchNextPage

useInfiniteQuery returns data.pages (every fetched page accumulated so far), a fetchNextPage function to trigger loading another page, and a hasNextPage boolean derived from getNextPageParam's last return value β€” enough to drive a 'Load More' button rendering all accumulated pages.

4Triggering fetchNextPage on Scroll

For true infinite scroll without a button, fetchNextPage can be paired with an IntersectionObserver watching a sentinel element near the bottom of the list, automatically triggering the next page fetch when it enters the viewport, guarded against duplicate or unnecessary calls.

5Step-by-Step Breakdown

Beyond a Single Page of Results. A social feed, a search results list, or a comment thread all share a pattern: load the first page, then load more as the user scrolls, appending to what's already there β€” not replacing it. useQuery alone fetches one thing per key; useInfiniteQuery is built specifically for this accumulating, paginated shape.

The getNextPageParam Function. useInfiniteQuery needs to know how to fetch the NEXT page from a given response β€” getNextPageParam receives the last-fetched page and returns whatever value (a cursor, a page number) should be passed to queryFn for the next request, or undefined if there are no more pages.

What does returning undefined from getNextPageParam signal to useInfiniteQuery?

  • β†’There are no more pages left to fetch
  • β†’The last request failed and should be retried

Fetching the Next Page with fetchNextPage. useInfiniteQuery returns data.pages (an array of every fetched page so far), fetchNextPage (a function to trigger loading the next one), and hasNextPage (a boolean derived from whether getNextPageParam last returned a real value). Call fetchNextPage from an 'Load More' button or an intersection observer.

Triggering fetchNextPage on Scroll. For true infinite scroll (no button), pair fetchNextPage with an IntersectionObserver watching a sentinel element at the bottom of the list β€” when it enters the viewport, call fetchNextPage() automatically, guarded by hasNextPage and !isFetchingNextPage to avoid duplicate calls.

Why guard the scroll-triggered fetchNextPage() call with hasNextPage && !isFetchingNextPage?

  • β†’To avoid firing duplicate requests or fetching past the last page
  • β†’It's purely a CSS/styling concern

Mastery Achieved. You now understand infinite queries: useInfiniteQuery for accumulating paginated data, getNextPageParam for determining what comes next (and when to stop), fetchNextPage/hasNextPage for driving a load-more button, and pairing it with an IntersectionObserver for true infinite scroll. This closes out Advanced Data Fetching β€” next, you'll move into Advanced Forms.

Level Up πŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

IntersectionObserver is fully supported in all modern browsers.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1Infinite Scroll Needs an Alternative for Keyboard and Screen Reader Users

Pure scroll-triggered loading can be hard to use for keyboard-only or screen reader users who don't scroll the same way sighted mouse users do β€” pairing infinite scroll with a visible, focusable 'Load More' button as a fallback keeps the experience accessible.

2Announce Newly Loaded Content

When a new page of items loads via infinite scroll, consider an aria-live region announcing that new content was added, so screen reader users are aware content beyond what they've already heard exists.

SEO Implications

  • 1

    Infinite Scroll Content May Not Be Crawlable Without Additional Handling

    Content loaded only via client-side scroll-triggered fetches after the initial page load may not be discovered by crawlers that don't scroll or execute the triggering JavaScript β€” pair infinite scroll with real paginated URLs or ensure critical content is present in the initial server-rendered response for SEO-sensitive pages.

Best Practices

Always Guard fetchNextPage Calls Against Duplicate Triggers

Check both hasNextPage and !isFetchingNextPage before calling fetchNextPage from a scroll observer, since intersection events can fire multiple times in quick succession.

Provide a Manual 'Load More' Fallback Alongside Scroll Triggers

Even with automatic infinite scroll, a visible, keyboard-accessible button gives users control and a fallback if the observer-based triggering doesn't fire as expected.

Frequent Bugs

THE BUG

Scrolling near the bottom of a list triggers several duplicate page fetches in quick succession.

THE FIX

The IntersectionObserver callback isn't checking hasNextPage and isFetchingNextPage before calling fetchNextPage, allowing multiple overlapping calls. Add both checks before triggering the next fetch.

THE BUG

The list stops loading more items even though the API clearly has more data available.

THE FIX

getNextPageParam is likely returning undefined too early, or not correctly extracting the next cursor/page number from the API's actual response shape. Verify it matches the real pagination metadata returned by the endpoint.

Real-World Examples

An Infinite-Scrolling Comment Thread

A comment section needs to load additional comments as the user scrolls down, accumulating them rather than replacing the list. useInfiniteQuery with a cursor-based getNextPageParam, combined with an IntersectionObserver on a sentinel div at the bottom of the rendered comments, provides seamless infinite scroll.

const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
  queryKey: ['comments', postId],
  queryFn: ({ pageParam }) => fetchComments(postId, pageParam),
  initialPageParam: null,
  getNextPageParam: (lastPage) => lastPage.nextCursor,
});

{data.pages.flatMap(page => page.comments).map(c => <Comment key={c.id} {...c} />)}
<div ref={sentinelRef} />

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

getNextPageParam doesn't match the API's actual pagination response shape

// If the API returns { items: [...], nextCursor: 'abc123' } getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,

The Solution //

Double-check the exact field name and structure the API returns for pagination metadata (like nextCursor, next_page, or a computed offset) and make sure getNextPageParam extracts it correctly from lastPage.

The Error //

An IntersectionObserver-driven fetchNextPage call fires multiple times for the same intersection event

if (entry.isIntersecting && hasNextPage && !isFetchingNextPage) { fetchNextPage(); }

The Solution //

Guard the call with both hasNextPage and !isFetchingNextPage checks, since intersection observer callbacks can fire more than once in quick succession as the sentinel element's visibility changes.

Lesson Glossary

[01]useInfiniteQuery

The hook for fetching and accumulating paginated data, page by page, into a single growing list.

Code Preview
useInfiniteQuery({ queryFn, getNextPageParam })

[02]getNextPageParam

A function returning the parameter for the next page fetch, or undefined if pagination is complete.

Code Preview
(lastPage) => lastPage.nextCursor

[03]fetchNextPage

The function returned by useInfiniteQuery to trigger loading the next page of data.

Code Preview
fetchNextPage()

[04]hasNextPage

A boolean indicating whether more pages are available to fetch, derived from getNextPageParam.

Code Preview
hasNextPage && <button>Load More</button>

[05]IntersectionObserver

A browser API for detecting when an element enters the viewport, used to trigger infinite scroll.

Code Preview
new IntersectionObserver(callback)

Continue Learning