Implementing Infinite Scroll Pagination with React-Query v3 | AntStack
profile picture Aditya K
7 min read May 30, 2023

Implementing Infinite Scroll Pagination with React-Query v3 | AntStack

Pagination is a common technique used to enhance performance and user experience when developing online apps that display vast volumes of data. While a traditional pagination provides structure, it often disrupts the flow of navigation. Users are forced to stop, click, and wait for the next set of result.

Infinite scroll, on the other hand, offers a contemporary kind of pagination. It allows endless scrolling, enabling users to browse through content without having to click on pagination links. To make this experience efficient and maintainable, developers need the right tooling.

React-Query v3 is a powerful library that simplifies data-fetching and state management in React applications, making it an ideal choice for implementing infinite scroll pagination.

In this blog post, we’ll look at how to build infinite scroll using React-Query (now TanStack Query) and TypeScript in a React application to improve user experience and handle paginated data.

Core Tools for Building Infinite Scroll

1. React-Query

React-Query is a powerful data-fetching and state management library for React. One of its notable features is the useInfiniteQuery hook, which simplifies the process of implementing infinite scroll pagination in React applications. In this tutorial, we will focus on using useInfiniteQuery to fetch and paginate data from an API in our React app.

2. Intersection Observer

IntersectionObserver is a web API that allows efficient detection of when an element is visible in the viewport or intersects with another element.

We will be using IntersectionObserver to implement pagination in our application.

  • We can create a ref for the last element in the list, and then use IntersectionObserver to observe this element.
  • When the last element becomes visible in the viewport, we can fetch more data to implement infinite scroll pagination.

This allows us to efficiently load more data as the user scrolls, providing a smooth and optimized pagination experience.

Using Infinite Scroll with React-Query v3

Let us start by creating a react app.

yarn create vite infinite-scroll-app --template react-ts

To use infinite scroll pagination with React-Query v3, you’ll need to install the library and set up your query. First, install React-Query v3 and axios, a popular library for making HTTP requests, using npm or yarn:

yarn add react-query axios

Once you have installed React-Query v3 and axios, the next step is to set up your query and implement the infinite scroll logic. Before diving into that, let’s clean up the App.tsx file by removing all unnecessary code.

//App.tsx
import './App.css';

function App() {
  return <div>Infinate Scroll App</div>;
}

export default App;

Then, we need to set up a QueryClientProvider at the top level of our application to provide the QueryClient instance to all the child components:

//main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from 'react-query';
import App from './App';
import './index.css';

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <QueryClientProvider client={queryClient}>
    <React.StrictMode>
      <App />
    </React.StrictMode>
  </QueryClientProvider>
);

We will now create a function to fetch data. To simulate pagination, we will use the PokeAPI as our endpoint.

//utils/fetchData.ts
import axios from 'axios';
import { QueryFunction } from 'react-query';

const LIMIT = 10

export interface ItemDataI {
  name: string;
}

interface APIResultsI {
  results: ItemDataI[];
  offset: number | null;
}

const fetchData: QueryFunction<APIResultsI, 'pokemon'> = async ({
  pageParam,
}) => {
  const offset = pageParam ? pageParam : 0;
  const data = await axios.get(
    `https://pokeapi.co/api/v2/pokemon?offset=${offset}&limit=${LIMIT}`
  );
  return {
    results: data.data.results,
    offset: offset + LIMIT,
  };
};

export default fetchData;

Next, let’s implement infinite scrolling. Start by implementing React-Query’s useInfiniteQuery hook.

//App.tsx
const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isLoading,
  } = useInfiniteQuery('data', fetchData, {
    getNextPageParam: (lastPage, pages) => lastPage.offset,
  });

The useInfiniteQuery hook returns an object containing the following properties:

  • data: the data returned by our fetchData function
  • error: any errors returned by fetchData function
  • fetchNextPage: a function that fetches next set of data
  • hasNextPage: a boolean indicating whether there is more data to fetch
  • isFetching: a boolean indicating whether data is currently being fetched
  • isLoading: a boolean indicating whether the query is currently loading. This will be true for only the initial load.

Additionally, the useInfiniteQuery hook includes a getNextPageParam function. This function determines the value of the pageParam parameter for the next page of data to fetch.

When using useInfiniteQuery from React-Query to fetch data, the returned data is usually an array of pages, where each page contains an array of items. To easily map through and render these items, we will flatten the data into a single array. We can use the useMemo hook to efficiently flatten the data and prevent unnecessary recalculations.

//App.tsx
const flattenedData = useMemo(
    () => (data ? data?.pages.flatMap(item => item.results) : []),
    [data]
);

To implement infinite scroll pagination, we need to create a reference for the last element in the list and set up an IntersectionObserver to detect when that element becomes visible on the screen. Once the last element is visible, we can trigger the fetchNextPage function to fetch more data.

//App.tsx
const observer = useRef<IntersectionObserver>();
  const lastElementRef = useCallback(
    (node: HTMLDivElement) => {
      if (isLoading) return;
      if (observer.current) observer.current.disconnect();
      observer.current = new IntersectionObserver(entries => {
        if (entries[0].isIntersecting && hasNextPage && !isFetching) {
          fetchNextPage();
        }
      });
      if (node) observer.current.observe(node);
    },
    [isLoading, hasNextPage]
  );

Finally, we render the list of items.

//App.tsx
if (isLoading) return <h1>Loading Data</h1>;

if (error) return <h1>Couldn't fetch data</h1>;

return (
    <div>
      <div>
        {flattenedData.map((item, i) => (
          <div
            key={i}
            ref={flattenedData.length === i + 1 ? lastElementRef : null}>
            <p>{item.name}</p>
          </div>
        ))}
      </div>
      {isFetching && <div>Fetching more data</div>}
    </div>
  );

We can use the isLoading and error returned by useInfiniteQuery to display loading and error messages, respectively.

To render the list, we simply map through the flattened data and return each item. We also attach lastElementRef to the last element in the list. This allows our IntersectionObserver to observe the last element and trigger fetchNextPage when it becomes visible on the screen.

And with that, we’ve successfully implemented a smooth infinite scroll using React-Query v3 and IntersectionObserver, providing a seamless pagination experience for your users.

Demo GIF

You can find the GitHub repository for the entire code here and StackBlitz.

Conclusion

In summary, implementing infinite scroll pagination with React-Query v3 and IntersectionObserver is a simple yet powerful feature that enhances the performance and user experience of your React applications.

By leveraging the capabilities of React-Query for data fetching and state management, and utilizing the built-in IntersectionObserver API for detecting when to fetch more data, you can create a smooth and efficient pagination implementation without the need for additional external libraries or complex logic.

This approach results in a seamless and responsive browsing experience for your users, making it a valuable addition to your web applications.

Useful links

  1. Infinite scrolling
  2. React-Query v3 - useInfinateQuery
  3. Intersection Observer
  4. Axios

FAQs

1. What advantages does React-Query’s useInfiniteQuery provide over a custom implementation?

useInfiniteQuery abstracts away common concerns like managing page state, caching responses, handling loading/error states, and detecting if more data is available. This lets developers focus on rendering and user experience instead of boilerplate data-fetching logic.

2. How does Intersection Observer improve performance compared to scroll event listeners?

Unlike scroll listeners that fire continuously and can degrade performance, Intersection Observer is event-driven. It efficiently notifies you only when a target element enters or leaves the viewport, reducing reflows and improving responsiveness.

3. Can I combine infinite scroll with manual “Load More” buttons?

Yes. You can still use fetchNextPage manually with a button, while also setting up Intersection Observer for auto-loading. This hybrid approach gives users flexibility and keeps accessibility in mind.

Application Modernization Icon

Explore limitless possibilities with AntStack's frontend development capabilities. Empowering your business to achieve your most audacious goals. Build better.

Talk to us

Author(s)

Tags

Your Digital Journey deserves a great story.

Build one with us.

Recommended Blogs

Cookies Icon

These cookies are used to collect information about how you interact with this website and allow us to remember you. We use this information to improve and customize your browsing experience, as well as for analytics.

If you decline, your information won’t be tracked when you visit this website. A single cookie will be used in your browser to remember your preference.