Modern Front-end Development: Trends and Best Practices
Introduction
Pagination is a common technique used to enhance performance and user experience when developing online apps that display vast volumes of data. A contemporary kind of pagination called infinite scroll, commonly referred to as endless scrolling, enables users to browse over content without having to click on pagination links. 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 and TypeScript in a React application to improve user experience and handle paginated data.
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.
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 for implementing 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 ourfetchData
functionerror
: any errors returned byfetchData
functionfetchNextPage
: a function that fetches next set of datahasNextPage
: a boolean indicating whether there is more data to fetchisFetching
: a boolean indicating whether data is currently being fetchedisLoading
: a boolean indicating whether the query is currently loading. This will betrue
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.
You can find the GitHub repository for the entire code here.
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.