As developers, aiming for users to have the best experience when navigating your application is one of the top priorities. In addition to writing code and implementing complex functionalities, there should be a conscious effort to ensure maximum user experience and high performance of the application.
As a front-end developer, every bit of code, function or logic comes down to rendering content to users, so the appropriate measures should be taken in rendering content in specific use cases.
Consider a scenario where a huge chunk of data is to be displayed, rendering all that data to the Document Object Model (DOM) at once could have negative effects especially on the application's overall performance.
One way to get around this is to implement pagination, which entails creating pages and rendering a particular length of content per page. Another solution to this, one which we will discuss in this blog post, is to implement React infinite scrolling which allows you to render more content to users only when they reach the bottom of the page.
In this tutorial, you will learn how to implement React infinite scrolling using React Query v5. Let's dive in!
React Infinite Scrolling - What's that about?
Infinite scrolling is a content rendering technique that displays content as the user continuously scrolls to the bottom of the page. One key difference between infinite scrolling and pagination, another content rendering technique, is that infinite scrolling does not require the user to click a button to load more content. It is simply dependent on the user's action of scrolling down. Interesting, isn't it?
React Query
React Query is a JavaScript state management library used for data fetching and caching in React applications. React Query offers the useInfiniteQuery
hook which abstracts the complexities of implementing infinite scrolling in applications.
Prerequisites
Just before we get into the technical aspect and implementation of logic, here are a few things that would be great to know to effectively follow through this tutorial:
Getting Started
We're going to dive right into the logic to implement React infinite scrolling but just before we do that, let's get a few things set up in our environment.
Run this command in your terminal to add install React in your environment:
npm create vite infinite-scroll-app react
The next step will be to install the react-query library which we will be using to implement the logic. Still on your terminal, run this command:
npm install react-query
We're quite set on the installation bit. Now let's make some tweaks to our main.jsx
file(this may be your index.jsx
file if you're using another build tool). Here's what the main.jsx
file looks like:
import * as React from "react";
import * as 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")!).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>);
Here, we imported the QueryClientProvider
component and the QueryClient
instance which basically allows you to use the built-in hooks in the react-query library by wrapping your App
component in it.
Fetching Data
Earlier, we discussed React query being a library for data fetching, that's the logic behind this - it controls how data is being served. To implement React infinite scrolling, we definitely have to scroll through something, some content or data of some sort. This implementation is incomplete without the presence of data. Now let's get that data! I'll be making use of the Poke API which gives some mock data for our usage.
const dataList = async ({
page,
}) => {
const offset = page ? page : 0;
const res = await fetch(
`https://pokeapi.co/api/v2/pokemon?offset=${offset}&limit=10`
);
const data = await res.json();
console.log("data = ", data);
return {
results: data.results,
offset: offset + 10,
};
};
export default dataList;
Here, we're fetching our data from the API and then we set the limit to 10. The expected behavior for this is to render 10 values on the screen when the page is initially clicked upon and then continuously add more values in tens since the offset is set to offset + 10
, you can toggle this value if you want to add more values when the user reaches the bottom screen.
Now that we have our data fetched from the API, we can proceed to implement React infinite scrolling.
Implement Scrolling
In this section, we will get into the logic of implementing React infinite scrolling.
The UseInfiniteQuery
hook
Here, we will use the useInfiniteQuery
hook from the React query library. Consider the block of code below:
const {
data,
error,
fetchNextPage,
hasNextPage,
isFetching,
isLoading,
} = useInfiniteQuery('data', dataList, {
getNextPageParam: (lastPage, pages) => lastPage.offset,
});
The useInfiniteQuery
hook is the foundation of this implementation as it provides properties around the:
- data, which contains the data from the API.
- error, which handles any error from fetching the data from the API.
- fetchNextpage, which fetches the next set of data to be displayed when the user reaches the bottom.
- hasNextpage, which checks to see if there's any more data to be displayed.
- isFetching, which shows when the data is currently being fetched.
- isLoading, which shows when the first set of data is being loaded from the API.
Detecting an Element's Position with IntersectionObserver
You might have been wondering how the DOM knows when the user scrolls to the bottom of the page. Well, that's where the IntersectionObserver API comes in! The IntersectionObserver API provides a way to detect an element's position relative to the root element or viewport. Consider the block of code below:
const handleObserver = useRef<IntersectionObserver>();
const lastElement = useCallback(
(element) => {
if (isLoading) return;
if (observer.current) handleObserver.current.disconnect();
handleObserver.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasNextPage && !isFetching) {
fetchNextPage();
}
});
if (element) handleObserver.current.observe(element);
},
[isLoading, hasNextPage]
);
This piece of code observes the user's motion on the screen. If the user reaches the bottom of the screen, the IntersectionObserver
is alerted and serves the next set of data. Technically, it takes note of the point where the last element in the data intersects and then if hasNextPage
is true, it fetches the new set of data to display.
Finishing up
The final bit of action is to render the data and logic to the DOM but just before we do that. Let's flatten the data from the API into a single array so it is easier to map through and display on the DOM. We can use React's useMemo()
hook for this:
const flatData = useMemo
() => (data ? data?.pages.flatMap(item => item.result) : []),
[data]
);
And then:
if (isLoading) return <h1>Just a second...</h1>;
if (error) return <h1>Oops! Something went wrong</h1>;
return (
<div>
<div>
{flatData.map((item, i) => (
<div
className="flex flex-col justify-center items-center [&>*]:my-5"
key={i}
ref={flatData.length === i + 1 ? lastElement : null}
>
<h1 className="text-5xl">{item.name}</h1>
</div>
))}
</div>
{isFetching && <div>Hold on...</div>}
</div>
And here we are. Added some error handling in there and Tailwind CSS styling to make the text look bigger.
Displaying Result
Here's what it looks like:
If you look at the scroll bar, you see that when it approaches the bottom which seems like the end, it moves up, a reaction to new data being loaded to the DOM.
Conclusion
In this tutorial, you have learnt about React query v5 and how to implement React infinite scrolling using React query v5. Feel free to make more tweaks and explore.
Stay up to date on React 18 with our comprehensive guide. Until then, Happy Coding!