[ReactJS]

17 Sep 2025

-

2 min read time

Infinite Scroll and Streaming Data with TanStack Query + React 19

Master infinite scrolling in React using TanStack Query’s `useInfiniteQuery` hook. Learn to handle loading, errors, debug pagination, and apply advanced techniques like concurrent rendering, real-time updates, memory management, accessibility, and SEO-friendly server-side rendering.

 Timothy St Ledger

By Timothy St Ledger

Infinite Scroll and Streaming Data with TanStack Query + React 19

Mastering Infinite Queries in React with TanStack Query

By the end of this guide, you’ll know how to implement infinite scrolling with TanStack Query, handle loading and error states, debug pagination issues, and apply advanced techniques—like React’s concurrent rendering, streaming updates, memory management, accessibility, and SEO-friendly server-side rendering.

Image

Getting Started with Infinite Queries

To fetch pages of data on demand, TanStack Query offers the useInfiniteQuery hook reference .

  1. Install TanStack Query

    npm install @tanstack/react-query
  2. Set up the QueryClient

    import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
    
    const queryClient = new QueryClient();
    
    function App() {
    
    return (
    
    <QueryClientProvider client={queryClient}>
    
    <YourComponent />
    
    </QueryClientProvider>
    
    );
    
    }
  3. Define your API call

    async function fetchItems({ pageParam = 1 }) {
    
    const res = await fetch(`/api/items?page=${pageParam}`);
    
    return res.json();
    
    }

Implementing `useInfiniteQuery`

import { useInfiniteQuery } from '@tanstack/react-query';


const {

  data,

  fetchNextPage,

  hasNextPage,

  isFetchingNextPage,

  status,

  error,

} = useInfiniteQuery(

  ['items'],

  fetchItems,

  {

    getNextPageParam: (lastPage) =>

    lastPage.nextPage ?? false,

  }

);
  • `getNextPageParam`: Return the next page number or `false` when done.

  • `fetchNextPage()`: Triggers loading of the next page.

  • `hasNextPage`: `true` if more pages are available.

  • `isFetchingNextPage`: `true` while the next page is loading.

Property

Description

data

Contains the loaded pages of data.

fetchNextPage

Function to trigger loading of the next page.

hasNextPage

Boolean indicating if more pages are available.

isFetchingNextPage

Boolean that's true while the next page is loading.

status

Status string representing the query's current state (e.g., 'loading').

error

Error object if the query failed, otherwise undefined.

Rendering and “Load More” Button

return (

  <div>

    {data.pages.map((page) =>

      page.items.map((item) => <Item key={item.id} data={item} />)

    )}

    <button

      onClick={() => fetchNextPage()}

      disabled={!hasNextPage || isFetchingNextPage}

    >

      {isFetchingNextPage

        ? 'Loading...'

        : hasNextPage

        ? 'Load More'

        : 'No More Items'}

    </button>

    {status === 'error' && <p>Error: {error.message}</p>}

  </div>

);

Handling Loading, Error, and Empty States

  • Global status: `status` can be `'loading'`, `'error'`, or `'success'`.

  • Page-level loading: use `isFetchingNextPage` and `hasNextPage`.

  • Empty data: Check `data.pages[0].items.length` before rendering.

Quote:

“Providing clear feedback during pagination ensures users aren’t left wondering if more content exists.” – Sarah Drasner

Debugging `getNextPageParam` Issues

When `fetchNextPage` never fires, check:

  1. API response shape

    Ensure JSON includes the page token or number:

    {
    
      "items": [...],
    
      "nextPage": 3
    
    }
  2. Correct key access

    getNextPageParam: (lastPage) => lastPage.nextPage
  3. Edge cases

    Return `false` or `undefined` when no more pages exist to stop further calls.

Issue

Description

Solution

Missing `nextPage` in API response

API does not return the next page token or number in the response.

Update API to include a `nextPage` field with each response.

Incorrect key access in getNextPageParam

Retrieving the wrong property from the API response for pagination.

Ensure `getNextPageParam` correctly accesses `lastPage.nextPage`.

Edge case handling: no more pages

Pagination does not stop when there are no more pages to fetch.

Return `false` or `undefined` from `getNextPageParam` to end fetching.

Advanced Techniques

React 19’s Concurrent Features and Infinite Scroll

React’s concurrent rendering (introduced in React 18) enables nonblocking UI updates during data fetching. The upcoming React 19 is expected to refine concurrent transitions, making infinite scroll smoother by:

  • Letting high-priority UI updates interrupt page loads

  • Preventing “jank” when appending new items

Integrating Real-Time Data Streams

For live feeds—chat, notifications, or stocks—combine WebSockets or Server-Sent Events with infinite queries:

Image

  1. Open a WebSocket

    const socket = new WebSocket('wss://example.com/stream');
  2. On message, invalidate queries

    socket.onmessage = () => {
    
    queryClient.invalidateQueries(['items']);
    
    };
  3. SSE alternative

    const evtSource = new EventSource('/api/stream');
    
    evtSource.onmessage = (e) => {
    
    queryClient.invalidateQueries(['items']);
    
    };

Sources:

WebSockets API (MDN)

WHATWG Server-Sent Events specification

Managing Memory with Cache Eviction and Windowing

When dealing with hundreds of pages:

  • Cache aging:

    new QueryClient({
    
      defaultOptions: {
    
      queries: { cacheTime: 1000  60  5 }, // 5 minutes
    
      },
    
    });
  • Windowing: Keep only the nearest pages in state. After appending new pages, drop the oldest:

    const pages = data.pages.slice(-10);

This prevents unbounded memory growth in long sessions.

Making Infinite Scroll Accessible

  • Wrap your list in a landmark and enable screen readers to announce new items:

    <div role="feed" aria-live="polite">
    
    {items.map(...)}
    
    </div>
  • Manage keyboard focus: Move focus to newly loaded content or keep it on the “Load More” button.

Refer to the WAI-ARIA Authoring Practices 1.1 for detailed guidance.

SSR and SEO Considerations

Server-rendering infinite lists ensures search engines index initial content:

  • Hydration: Pre-fetch the first page on the server.

    const dehydratedState = dehydrate(queryClient);
  • Streaming SSR in React 18:

    <Root>
    
      <React.Suspense fallback={<Loading />}>
    
        <YourApp />
    
      </React.Suspense>
    
    </Root>

    Browser receives HTML as it’s generated, improving Time to First Byte.

See the Streaming Server Rendering section of the React 18 blog for full details.

For SEO, include proper `<link rel="next">` and `<link rel="prev">` tags in your `<head>` for crawlers.

Final Scroll Tips

By combining TanStack Query’s infinite query API with React’s concurrent features, streaming updates, cache controls, accessibility best practices, and SSR strategies, you’ll build lists that feel both fast and robust. Start experimenting today to deliver endless content without overwhelming users or servers.

 Timothy St Ledger

By Timothy St Ledger

More from our Blog

Keep reading