Doing pagination with React Query
Published: 2023-01-19 01:41:05

by [Bobate Olusegun](https://blog.openreplay.com/authors/bobate-olusegun)

When getting data from a large dataset (an API or a database), developers frequently struggle with presenting it to the user while keeping in mind specific requirements, such as a short load time and an excellent user interface.

Pagination is one method of resolving this. Using pagination enables developers to separate the data into pages for the end-users. As a result, chunks of large datasets are displayed on different pages, and as the user interacts with the pagination controls, data for each page is fetched and displayed. Pagination is a feature frequently found in applications like learning management systems, product sites (eCommerce with dozens of goods), blog pages, Google search results, and any page with massive data results.

Constructing the logic to perform pagination can be daunting, as you'll have to introduce some mathematical programming techniques coupled with React hooks. However, I'll show you how to use React-Query to implement pagination in this article.



## Building the application

I have created a starter template repository to bootstrap the build process. You will need to clone this GitHub repository to your local desktop.

First, open your terminal and change the directory to where you want to save the app folder. For example, I want mine to be on the desktop, and I will run the following command.

```
cd desktop
```

Next, clone the starter application to our desktop:

```
git clone https://github.com/shegz101/Pagination-React-Query.git
```

Your folder structure should have the following directories and files:

![Folder Structure](https://blog.openreplay.com/images/doing-pagination-with-react-query/images/yKFopJl.jpg)

### Installing dependencies
The application has node packages attached to it, and for the build to run smoothly, we must download all the needed dependencies. To do this, run the following command:

```
cd Pagination-React-Query
npm install
```

After running the command above, a node module directory gets added to your folder structure.

### Disconnect from the Remote Git Repository

Since this application is now your setup, and you may like to push it to your GitHub account as your project, you must disconnect it from the remote repository (i.e., the repo you cloned the starter folder from).

You have first to check if this folder is connected to any remote repository by running this:

```
git remote -v
```

You will get the links in the snapshot below as the result of the command above.

![repo connected](https://blog.openreplay.com/images/doing-pagination-with-react-query/images/tRHiZ31.jpg)


The snapshot above tells us that the folder is connected to the git repository link returned.

Now, to disconnect the folder, run the command below:

```
git remote remove origin
```
The folder is now disconnected. If you run `git remote -v`, you notice no repository link is returned (proof that the folder has been disconnected).

### Starting the application

To run this application, type this command in your terminal:
```
//not npm start since we used vite and not CRA
npm run dev 
```

The command above will compile the application and host it at "http://localhost:5173/". Simply click on the localhost link, and the application will open in your browser.

Here is what your browser will display:

![Posts Overview](https://blog.openreplay.com/images/doing-pagination-with-react-query/images/pagination.gif)



So far, what we have done in the app is fetch and display the data. With Axios, a package used for HTTP requests, we could fetch data from the API and display it. Try to understand what the starter files entail and how we've been able to get to this point (the snapshot above) because this would form the basis of your foundation for this web build, thus the prerequisites.

We can now move into the central part of this article, which is how to implement pagination using React-Query. I assume you understand the build-up to this point. Let's get into the nitty-gritty of this article.

Remember to tidy up your code before starting to use React Query. Simply deleting the `posts` state from `Posts.jsx` and the `useEffect` we used to collect the data will accomplish this. Remove the code that follows:

```javascript
const [posts, setPosts] = useState([]);

useEffect(() => {
  getPosts(page).then((json) => setPosts(json));
}, [page]);
```




<h2>Open Source Session Replay</h2> 

_<a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a> is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data._

![OpenReplay](https://blog.openreplay.com/banner-blog_1oYPGT.png)

_Start enjoying your debugging experience - <a href="https://github.com/openreplay/openreplay" >start using OpenReplay for free</a>._



## Initialising React Query

The official [documentation](https://tanstack.com/query/v4/docs/overview) describes React Query as the missing data-fetching library for React. Still, in more technical terms, it makes fetching, caching, synchronizing, and updating server state in your React applications a breeze. In simple terms, React Query is powerful asynchronous state management for React. 

You first need to install the React Query library to get access to the React Query pagination feature. You can do this by running the following command:
```
npm i react-query
```

Next, navigate to the `Posts.jsx` file and import the `useQuery` hook from React Query.

```javascript
import { useQuery } from 'react-query';
```

For the best experience, use the `useQuery` hook and set the `KeepPreviousData` option to true. We can then access states like:
- `isPreviousData` - This lets us know the data the query is currently providing. 
- `isLoading` - This lets us know if the data to be fetched is loading 
- `isError` - This lets us know if an error exists
- `error` - returns the error object 
- `data` - This is the result we get and display on each page 
- `isFetching` - This is the fetching status.

It should be noted that every `useQuery` state with the 'is' prefix produces a boolean value (true or false). Additionally, the UI is maintained via the `KeepPreviousData` option even though several queries are made and discarded for each page of data read.

The `useQuery` takes two parameters - the queryKey, and the queryFn. The queryKey is a pair of elements in an array. The first element depends on the URL; navigate to the `axios.js` file and find the line of code that reads `const resp = await axiosRequest.get('/posts?_page=${pageParam}');`, what comes before the `question mark (?)` which is `/posts` is the first element and the second is the page state. While the queryFn comprises calling the function to fetch data from the API and setting the `keepPreviousData` to `true`. The queryKey is what React Query utilizes to cache the data for each page, and the queryFn is used to fetch the data.

Let's put the previous information into practice. Since we are now utilizing React Query to obtain the data, we will no longer use the 'useEffect' hook as we previously did. Additionally, since the "posts" array is no longer defined, we won't be mapping through it; instead, we'll be mapping through the data array that react-query produces.

This is what your  `Posts.jsx` file should look like:

```javascript
import "../App.css";
import { getPosts } from "../api/axios";
import { useState } from "react";
import { useQuery } from "react-query";

const Posts = () => {
  const [page, setPage] = useState(1);

  const { isLoading, isError, error, data, isFetching, isPreviousData } =
    useQuery({
      queryKey: ["/posts", page],
      queryFn: () => getPosts(page),
      keepPreviousData: true,
    });

  return (
    <div className="posts__section">
      <div className="pages__section">
        <button>prev</button>
        <button>1</button>
        <button>2</button>
        <button>3</button>
        <p>Page {page}</p>
        <button>4</button>
        <button>5</button>
        <p>...</p>
        <button>10</button>
        <button>Next</button>
      </div>

      <div className="blog__posts">
        <div className="posts">
          {data.map((post) => (
            <div className="post">
              <p>{post.id}</p>
              <p>{post.title}</p>
              <p>{post.body}</p>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

export default Posts;
```

### Fixing the No QueryClient Error
Finally, React-Query is all set. But there is one crucial piece missing. If you check your browser's developer tools, you should see a warning in the `Console` -tab, which says No Queryclient declared.
Let's fix that.

- First, reinstall React-Query with this command:
```
npm i @tanstack/react-query
```
Next, once you've reinstalled the library using the npm command above, you'll also have to re-import the library into your React component by replacing this command:
```
import { useQuery } from 'react-query'
```

With this command:

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

We can now override the error we were getting because we have access to the QueryClient.

- Next, create a client.
 
It is advisable to create the queryClient outside of your component (Posts), so as not to get your cache (generated from the queryCache, which is embedded in the queryClient) thrown away whenever things like re-rendering occur in the component. This makes it referentially stable. 
Declare a 'queryClient' variable outside of your component to accomplish this:

```javascript
//Create a Client
const queryClient = new QueryClient()
```

The next thing is to declare the QueryClient Provider, which creates a context to store the QueryClient and makes it accessible throughout the app.

Declare the Provider and export it as default using the following code:
```javascript
const PostApp = () => {
  return (
    <QueryClientProvider client={queryClient}>
      <Posts />
    </QueryClientProvider>
  );
};

export default PostApp;
```

Navigate to your `App.jsx` file and import the Provider. Your code block in the `App.jsx` file should look like this:
```javascript
import React from "react";
import "./App.css";
import PostApp from "./components/Posts";

function App() {
  return (
    <div className="app">
      <h1 className="app__header">Random Generated Posts.</h1>
      <PostApp />
    </div>
  );
}

export default App;

```

### Implementing Pagination using the Query Result

The query result is returned by `useQuery,` and contains the information/states needed to use the data. Navigate to the Posts component (Posts.jsx) file; that's where we would implement pagination.

We would first map through the data outside of the return block. Rendering of data will be based explicitly on the states/status react-query provides. We want to render data only when it isn't in the loading/fetching state, and there aren't any errors while fetching data. This can be done with the help of a control statement - the if, else if, and else statements.

Create a variable for mapping through the data:
```javascript
const newFetch = (
  <div className="posts">
    {data.map((post) => (
      <div className="post">
        <p>{post.id}</p>
        <p>{post.title}</p>
        <p>{post.body}</p>
      </div>
    ))}
  </div>
);
```

Next, create a function that handles data rendering using control statements.
```javascript
const isPostFetched = () => {
  if (isLoading || isFetching) {
    return (
      <p style={{ color: "white", fontSize: "30px", marginTop: "20px" }}>
        Loading Posts....
      </p>
    );
  } else if (isError) {
    return (
      <p style={{ color: "white", fontSize: "30px", marginTop: "20px" }}>
        The Error is: {error.message}
      </p>
    );
  } else {
    return newFetch;
  }
};
```

To determine what should be rendered on the webpage, we are just checking the status of the data in the code above. We first check if it is in the fetching or loading state, where it should display `loading posts....`, then we check whether there is an error; if so, we display the error message, and finally, if it is not in any of the aforementioned states, it should provide us with the actual data.

We can now call the `isPostFetched` function inside the return block to render data on the web page.

```html
<div className="blog__posts">{isPostFetched()}</div>;
```

### The Button Functions

Be aware that the functions for the buttons would be hard coded because the API doesn't return certain helpful information that would have allowed us to perform the task more efficiently. 
The functions are as follows:
```javascript
const prevPage = () => setPage((prev) => prev - 1);
const pageOne = () => setPage(1);
const pageTwo = () => setPage(2);
const pageThree = () => setPage(3);
const pageFour = () => setPage(4);
const pageFive = () => setPage(5);
const pageTen = () => setPage(10);
const nextPage = () => setPage((next) => next + 1);
```

Next, update the `div` where the buttons are embedded to this:

```html
<div className="pages__section">
  <button onClick={prevPage} disabled={isPreviousData || page === 1}>
    prev
  </button>
  <button onClick={pageOne}>1</button>
  <button onClick={pageTwo}>2</button>
  <button onClick={pageThree}>3</button>
  <p>Page {page}</p>
  <button onClick={pageFour}>4</button>
  <button onClick={pageFive}>5</button>
  <p>...</p>
  <button onClick={pageTen}>10</button>
  <button onClick={nextPage} disabled={isPreviousData || page === 10}>
    Next
  </button>
</div>;
```
The function for each button was activated in the `div` above using the `onClick` event. We also implemented a feature that would disable the `prev` button if we were on the first page and the `next` button if we were on the last page.

Navigate to the `App.css` file and add this code:

```css
button: disabled {
  opacity: 0.5;
  cursor: not - allowed;
}
```

The style code above reduces the opacity of the button color when it is disabled.

### Final Thoughts

The function that triggers each page button to display data was hard coded in this case because the API we used did not return the value of the total number of pages for us.
When building an API for a webpage that uses pagination, be sure to return the following data: the current page, the amount of data that will be displayed on each page, the total amount of data being retrieved from the API, and the number of pages based on the formula: total data generated minus total data to be displayed on each page.

If these are provided, you can easily get the pages array:

```javascript
const pgArray = Array(totalpages {number of pages})
  .fill()
  .map((_, id) => id + 1 {id + 1 because index are zero based});
```

Render the page buttons by referencing the PgButtons component.

```javascript
{
  pgArray.map((pageNumber) => (
    <PgButtons
      key={pageNumber}
      pageNumber={pageNumber}
      setPage={setPage}
      isPreviousData={isPreviousData}
    />
  ));
}
```

Next, you can create a separate component to display the buttons to keep things clean. Pass the `page number`, the `setPage`, and `isPreviousData` as props.

```javascript
const PgButtons = ({ pageNumber, setPage, isPreviousData }) => {
  return (
    <button onClick={() => setpage(pg)} disabled={isPreviousData}>
      {pageNumber}
    </button>
  );
};
```

That's how the buttons should be implemented and not hard coded.


### Overview of the Posts Component
If your application still has trouble rendering, check your code in the `Posts.jsx` file against the following:
```javascript
import "../App.css";
import { getPosts } from "../api/axios";
import { useState } from "react";
// import { useQuery } from 'react-query';
import {
  QueryClient,
  QueryClientProvider,
  useQuery,
} from "@tanstack/react-query";

//Create a Client
const queryClient = new QueryClient();

const Posts = () => {
  const [page, setPage] = useState(1);

  const { isLoading, isError, error, data, isFetching, isPreviousData } =
    useQuery({
      queryKey: ["/posts", page], //used internally for refetching, caching, and sharing your queries throughout your application.
      queryFn: () => getPosts(page),
      keepPreviousData: true,
    });

  const newFetch = (
    <div className="posts">
      {data.map((post) => (
        <div className="post">
          <p>{post.id}</p>
          <p>{post.title}</p>
          <p>{post.body}</p>
        </div>
      ))}
    </div>
  );

  const isPostFetched = () => {
    if (isLoading || isFetching) {
      return (
        <p style={{ color: "white", fontSize: "30px", marginTop: "20px" }}>
          Loading Posts....
        </p>
      );
    } else if (isError) {
      return (
        <p style={{ color: "white", fontSize: "30px", marginTop: "20px" }}>
          The Error is: {error.message}
        </p>
      );
    } else {
      return newFetch;
    }
  };

  const prevPage = () => setPage((prev) => prev - 1);
  const pageOne = () => setPage(1);
  const pageTwo = () => setPage(2);
  const pageThree = () => setPage(3);
  const pageFour = () => setPage(4);
  const pageFive = () => setPage(5);
  const pageTen = () => setPage(10);
  const nextPage = () => setPage((next) => next + 1);

  return (
    <div className="posts__section">
      <div className="pages__section">
        <button onClick={prevPage} disabled={isPreviousData || page === 1}>
          prev
        </button>
        <button onClick={pageOne}>1</button>
        <button onClick={pageTwo}>2</button>
        <button onClick={pageThree}>3</button>
        <p>Page {page}</p>
        <button onClick={pageFour}>4</button>
        <button onClick={pageFive}>5</button>
        <p>...</p>
        <button onClick={pageTen}>10</button>
        <button onClick={nextPage} disabled={isPreviousData || page === 10}>
          Next
        </button>
      </div>

      <div className="blog__posts">{isPostFetched()}</div>
    </div>
  );
};

const PostApp = () => {
  return (
    <QueryClientProvider client={queryClient}>
      <Posts />
    </QueryClientProvider>
  );
};

export default PostApp;

```

## Conclusion
In this article, we learned how to implement pagination using [react-query](https://react-query-v3.tanstack.com/). We learned about the perks of using react-query for handling pagination in our web apps.



> A TIP FROM THE EDITOR: If you want infinite scrolling instead of paging, don't miss [Infinite Scrolling With React Query](https://blog.openreplay.com/infinite-scrolling-with-react-query/).







[![newsletter](https://blog.openreplay.com/newsletter_Z1P0UIP.png)](https://newsletter.openreplay.com/)