Building React Infinite Scroll with useInfiniteQuery
Building React Infinite Scroll with useInfiniteQuery

In this post you will learn how to build infinite loading of data inside React.

Finished project

The most flexible and comfortable way to implement it inside React is by using two libraries. First of all we want to use React Query. If you don't know what it is it's a library that synchronise API data with local state inside the component. It is really amazing when you need to make API call and you avoid useEffect completely.

It also has caching and refetching but for infinite scroll we are interested in useInfiniteQuery hook that is available inside this library.

It allows us in a comfortable way to get data for next pages.

I would say it is 70% of the logic that we need to implement for infinite scrolling. Another 30% is a library which is called react-infinite-scroll-component. It doesn't do anything regarding API at all. This is just a client side. What it does it listens for the bottom of the page and triggers what you tell it to get new data.

It calculates the position of bottom screen and loads new data when we reached it.

These 2 libraries is the best solution that I know if you need infinite loading inside React.

Installation & Configuration

First of all let's install both libraries.

npm i react-infinite-scroll-component @tanstack/react-query

Our first step now is to configure React Query correctly. In order to do that we must wrap the whole application with it.

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root")).render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>
);

Here we wrapped App with QueryClientProvider and provided a queryClient to it. Now we can use React Query from any place of our application.

Fetching data

We will implement the whole logic of infinite loading inside App component. And we will use a public API in order to get data.

// src/App.jsx
const getArticles = async ({ pageParam = 0 }) => {
  const res = await fetch(
    `https://api.realworld.io/api/articles?limit=10&offset=${pageParam}`
  );
  const data = await res.json();
  return { ...data, prevOffset: pageParam };
};

Here on the top of our App outside of the component we created a function which is responsible for getting articles from the API. Most importantly it gets an object with pageParam and uses it to get data. As you can see we don't just return data back but also prevOffset which is our pageParam.

As we use this function later inside useInfiniteQuery we must know what was our previous offset so we can call getUsers with new offset. Also inside data we don't just get an array of items but articles and articlesCount.

Adding React Query

Let's use useInfiniteQuery hook inside our App component now.

const App = () => {
  const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
    queryKey: ["users"],
    queryFn: getUsers,
    getNextPageParam: (lastPage) => {
    },
  });
  ...
}

Inside useInfiniteQuery we must provide 3 parameters. First of all is queryKey. This is a property where we store and cache data inside React Query. Second is queryFn where inside we provide our getUsers function which makes an API call to the backend. The third parameter is a getNextPageParam which has lastPage data as an argument and must return new pageParam. This param which we didn't return yet will be passed inside our getUsers function.

Now let's write the logic of getNextPageParam.

getNextPageParam: (lastPage) => {
  if (lastPage.prevOffset + 10 > lastPage.articleCount) {
    return false;
  }

  return lastPage.prevOffset + 10;
},

We return here false if our prevOffset + 10 is bigger that total articleCount. It means that we don't have any more pages and articles. We ended infinite scroll. In other case we increase our offset by 10 to get next 10 articles.

Let's check if it's working.

React Query

As you can see React Query successfully loaded first page with data this is why we see pages[0] with articles, articlesCount and prevOffset.

Preparing articles

But the format is not really comfortable to render as we have not a flat list but the list of pages with articles inside every page. It would be nice to create a flat list with all articles to render.

const articles = data?.pages.reduce((acc, page) => {
  return [...acc, ...page.articles];
}, []);
console.log(articles);

Here we went though all pages and put all articles in a single array.

Flat articles

As you can see in browser we get a flat list of articles that we loaded. This is much easier to render.

Client side

Actually we could just use React Query for the business logic and write client logic with listening scrolling to the bottom. But in order to avoid building that we can use the second library which is react-infinite-scroll-component.

Now we just need to import it and provide correct data inside.

import InfiniteScroll from "react-infinite-scroll-component";

const App = () => {
  return (
    <div>
      <h1>Hello monsterlessons</h1>

      <InfiniteScroll
        dataLength={articles ? articles.length : 0}
        next={() => fetchNextPage()}
        hasMore={hasNextPage}
        loading={<div>Loading...</div>}
      >
        <div>
          {articles &&
            articles.map((article, index) => (
              <div key={index} className="element">
                {article.title}
              </div>
            ))}
        </div>
      </InfiniteScroll>
    </div>
  );
};

We render InfiniteScroll component and render our articles inside. We provide dataLength which contains the total length of our articles. next is an attribute which will fetchNextPage when InfiniteScroll sees that we got to the bottom of the page. It also needs hasMore to know if we have more pages to fetch.

Inside InfiniteScroll we render all our articles which are already loaded.

Finished project

As you can see in browser every time when we scroll to the bottom of the page InfiniteScroll trigger fetch through React Query and loads data. Also in API call our offset is being increased every single time to get new articles.

And actually if you are interested to learn how to build real React project from start to the end make sure to check my React hooks course.

📚 Source code of what we've done