Building React Infinite Scroll with useInfiniteQuery
In this post you will learn how to build infinite loading of data inside React.
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.
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.
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.
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.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done