Here's a quick summary of everything we released in Q1 2024.

How to Implement Pagination in React

In this article, we'll look at how to implement pagination in React by retrieving content from Hygraph and dividing it across multiple pages.
Joel Olawanle

Joel Olawanle

Aug 23, 2022
Mobile image

#How to Implement Pagination in React

React is a JavaScript front-end library for creating user interfaces that are free and open source. A user interface can be anything that runs on the internet, such as a website or a web application. This website or web application may contain many contents, necessitating pagination to assist users in locating content and keeping our pages from becoming overburdened with content.

#What Is Pagination, and Why Is It Important,[object Object]

Pagination is the process of dividing a website's content into a series of pages with similar content. This improves the user experience because the initial page load is faster than loading all the content simultaneously.

Pagination is an excellent method for organizing website content into separate pages so users can find the desired page/content. It is a feature we can use on a blog page, a product page, or any other page with a lot of content that we want to distribute across multiple pages.

#Pagination in React

Implementing pagination in React can be time-consuming because it necessitates some logic. This guide will look at two approaches to implementing this logic in React: from scratch and using a React package.

Before we begin implementing pagination, let's build a mini-project with Hygraph in which we consume a large amount of data and divide it into several pages with a set number of data per page.

#Building a Blog Page

We'd create a page with a list of blog posts and a link to where we can read them. For example, if an author has content published across many platforms, this could be the best way to join all his/her content into a single blog page, making it easier for people to access.

blog pagination

Because this article is about pagination, we'll go over the app creation briefly, and if you want to look at the source code, you can find it at this GitHub repository. Also, here is a live link to check it out.

Building Markup Page - App.js

We would handle all logic and display all of our content in our App.js without needing other components; however, you may choose to split this into components depending on your needs. This is how the markup looks:

// App.js
import React, { useState, useEffect } from 'react';
const posts = [
{
id: 1,
title:
'How to Internationalize a React Application Using i18next and Hygraph',
excert:
'In this post, we will take a deep dive into how to internationalize a React Application using i18next and Hygraph',
postUrl: 'https://hygraph.com/blog/react-internationalization',
cover: {
url: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60',
},
datePublished: '2020-01-01',
author: {
profilePicture: {
url: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60',
},
},
},
];
const App = () => {
const [blogPosts, setBlogPosts] = useState(posts);
return (
<div className="container">
<div className="title">
<h1>Blog</h1>
</div>
{blogPosts ? (
<div className="blog-content-section">
<div className="blog-container">
{blogPosts.map((blogPost) => (
<div className="blog-post" key={blogPost.id}>
<img className="cover-img" src={blogPost.cover.url} alt="" />
<h2 className="title">{blogPost.title}</h2>
<p className="description">{blogPost.excert}</p>
<div className="card-details">
<div className="lh-details">
<img
className="author-img"
src={blogPost.author.profilePicture.url}
alt=""
/>
<p className="date">
{new Date(`${blogPost.datePublished}`).toLocaleDateString(
'en-us',
{
year: 'numeric',
month: 'short',
day: 'numeric',
}
)}
</p>
</div>
<a
href={blogPost.postUrl}
target="_blank"
rel="noopener noreferrer"
className="read-more"
>
Read post
</a>
</div>
</div>
))}
</div>
</div>
) : (
<div className="loading">Loading...</div>
)}
</div>
);
};
export default App;

Note: We are using static data to populate our page with content, which is why we created the posts array, but such content would be best retrieved from an API built with Hygraph. This repository also contains the styles in the index.css file.

Here is my schema: I created two models, one for posts and one for authors, which are linked to each other in a two-way reference.

Posts Schema

posts schema

Author Schema

author schema

Fetching Content from Hygraph

In react, querying is done with **graphql-request** and for us to do that in React, we first need to install the dependency by running the command below:

npm i graphql-request

After it has been successfully installed, we will import it into our App.js file so that we can query the Hygraph endpoint:

import { request } from 'graphql-request';

Querying with GraphQL in React

At this point, it is assumed that we have our Hygraph endpoint and our GraphQL query; my query looks like this:

{
posts {
id
title
excert
postUrl
cover {
url
}
datePublished
author {
firstName
profilePicture {
url
}
}
}
}

Note: In the above query, we are requesting that all blog posts be saved with all the information we will be using, such as the cover image, title, author image, and much more.

At this point, this is what our React app - App.js would look like:

import React, { useState, useEffect } from 'react';
import { request } from 'graphql-request';
const App = () => {
const [blogPosts, setBlogPosts] = useState([]);
useEffect(() => {
const fetchBlogPosts = async () => {
const { posts } = await request(
'https://api-us-east-1.hygraph.com/v2/cl3zo5a7h1jq701xv8mfyffi4/master',
`
{
posts {
id
title
excert
postUrl
cover {
url
}
datePublished
author {
firstName
profilePicture {
url
}
}
}
}
`
);
setBlogPosts(posts);
};
fetchBlogPosts();
}, []);
return (
<div className="container">
<div className="title">
<h1>Blog</h1>
</div>
{blogPosts ? (
<div className="blog-content-section">
<div className="blog-container">
{blogPosts.map((blogPost) => (
<div className="blog-post" key={blogPost.id}>
<img className="cover-img" src={blogPost.cover.url} alt="" />
<h2 className="title">{blogPost.title}</h2>
<p className="description">{blogPost.excert}</p>
<div className="card-details">
<div className="lh-details">
<img
className="author-img"
src={blogPost.author.profilePicture.url}
alt=""
/>
<p className="date">
{new Date(`${blogPost.datePublished}`).toLocaleDateString(
'en-us',
{
year: 'numeric',
month: 'short',
day: 'numeric',
}
)}
</p>
</div>
<a
href={blogPost.postUrl}
target="_blank"
rel="noopener noreferrer"
className="read-more"
>
Read post
</a>
</div>
</div>
))}
</div>
</div>
) : (
<div className="loading">Loading...</div>
)}
</div>
);
};
export default App;

Note: Replace the API Endpoint and possibly the query with your own, and tinker with the UI if desired.

At this point, we have all the contents we populated our Hygraph database with on our app, which would be too much to fetch at once into our website if we had up to 100. Let's now use Pagination to help us divide this into multiple pages.

#Implementing Pagination in React

To implement pagination, we would need to create and obtain some data that we would use to implement this pagination, such as the number of posts per page and the index of both the first and last posts of a specific page, so that we can use the slice() method to ensure that only the following content is displayed:

import React, { useState, useEffect } from 'react';
import { request } from 'graphql-request';
const App = () => {
const [blogPosts, setBlogPosts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(3);
// ...
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = blogPosts.slice(indexOfFirstPost, indexOfLastPost);
return (
<div className="container">
{/* ... */}
</div>
);
};
export default App;

Note: To make this code easier to understand, we removed the useEffect() and markup. I'll keep doing something like this throughout this article, so check this repository whenever you're confused.

In the preceding code, we created two states: one to hold the current page and another to hold the number of posts we want to display on a page. To get the total number of pages, we simply divide the total number of content by the number of posts per page, which we will do shortly.

We also created variables to store the index of a page's first and last post. For example, if we want to show three posts per page, the indexOfLastPost of page 2 will be 6 and the indexOfFirstPost will be 3.

Now that we've used the splice() method to help us get the content we want to display on each page, make sure we change our array from blogPosts to currentPosts or any variable we use to store the spliced array.

When we change the currentPage's state value, the following happens to the content on that page:

pagination in react

We now need to add a page number to the bottom of our page so that users can easily navigate through these pages.

This can be accomplished either manually by creating a component to handle all the logic, or by installing a package to handle all the logic for us. In this article, we'll look at both options, but first, let's handle it manually by creating a paginate.js component.

Pagination component

We would receive two props for now from the App.js component (parent component) in the pagination component: postsPerPage and totalPosts. These data will assist us in determining the total number of pages for our content.

After receiving this data, we will create an empty array to store the page numbers from the loop:

import React from 'react';
const Paginate = ({ postsPerPage, totalPosts }) => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalPosts / postsPerPage); i++) {
pageNumbers.push(i);
}
return (
<div className="pagination-container">
{/* ... */}
</div>
);
};
export default Paginate;

Essentially, the code above loops through the based on the number of pages to get the numbers as 1,2,3... and then pushes it to an array that we can now loop through in our app:

import React from 'react';
const Paginate = ({ postsPerPage, totalPosts }) => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalPosts / postsPerPage); i++) {
pageNumbers.push(i);
}
return (
<div className="pagination-container">
<ul className="pagination">
{pageNumbers.map((number) => (
<li key={number} className="page-number">
{number}
</li>
))}
</ul>
</div>
);
};
export default Paginate;

At this point, our page numbers are visible on our application. The next step would be to add a click event to the page numbers, which would allow us to change the currentPage state. This function would be written in the parent component (App.js) and passed down as a prop:

// Navigate.js
import React from 'react';
const Paginate = ({ postsPerPage, totalPosts, paginate }) => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalPosts / postsPerPage); i++) {
pageNumbers.push(i);
}
return (
<div className="pagination-container">
<ul className="pagination">
{pageNumbers.map((number) => (
<li
key={number}
onClick={() => paginate(number)}
className="page-number"
>
{number}
</li>
))}
</ul>
</div>
);
};
export default Paginate;

We added a click event to the page number's button, as well as a method that takes the page number and passes it to the App.js component:

import React, { useState, useEffect } from 'react';
import { request } from 'graphql-request';
import Paginate from './Paginate';
const App = () => {
const [blogPosts, setBlogPosts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(3);
// ...
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = blogPosts.slice(indexOfFirstPost, indexOfLastPost);
const paginate = (pageNumber) => {
setCurrentPage(pageNumber);
};
return (
<div className="container">
<div className="title">
<h1>Blog</h1>
</div>
{blogPosts ? (
<div className="blog-content-section">
{/* ... */}
<Paginate
postsPerPage={postsPerPage}
totalPosts={blogPosts.length}
paginate={paginate}
/>
</div>
) : (
<div className="loading">Loading...</div>
)}
</div>
);
};
export default App;

You will notice we added this method, which helps us set the currentPage to the number received:

const paginate = (pageNumber) => {
setCurrentPage(pageNumber);
};

At this point, when we click the buttons, it changes the content based on the page:

changing the pages

So far, we've been able to use React to implement pagination. Although this looks good and allows us to navigate through all of our contents, we should add Next and Prev (previous) buttons. We can easily do this way and then add a onClick() event that will be handled in our parent component, just like we did earlier:

import React from 'react';
const Paginate = ({ postsPerPage, totalPosts, paginate, previousPage, nextPage }) => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalPosts / postsPerPage); i++) {
pageNumbers.push(i);
}
return (
<div className="pagination-container">
<ul className="pagination">
<li onClick={previousPage} className="page-number">
Prev
</li>
{pageNumbers.map((number) => (
<li
key={number}
onClick={() => paginate(number)}
className="page-number"
>
{number}
</li>
))}
<li onClick={nextPage} className="page-number">
Next
</li>
</ul>
</div>
);
};
export default Paginate;

Essentially, we added two new lists before and after the list iteration, as well as two events that we would handle on our App.js page:

import React, { useState, useEffect } from 'react';
import { request } from 'graphql-request';
import Paginate from './Paginate';
const App = () => {
//...
const previousPage = () => {
if (currentPage !== 1) {
setCurrentPage(currentPage - 1);
}
};
const nextPage = () => {
if (currentPage !== Math.ceil(blogPosts.length / postsPerPage)) {
setCurrentPage(currentPage + 1);
}
};
return (
<div className="container">
<div className="title">
<h1>Blog</h1>
</div>
{blogPosts ? (
<div className="blog-content-section">
{/* ... */}
<Paginate
postsPerPage={postsPerPage}
totalPosts={blogPosts.length}
paginate={paginate}
previousPage={previousPage}
nextPage={nextPage}
/>
</div>
) : (
<div className="loading">Loading...</div>
)}
</div>
);
};
export default App;

When we load our app at this point, both the next and previous (previous) buttons will now work and will not work when the max and min numbers are reached, as determined by the if statement we added in the methods.

Using react-paginate package

The react-paginate package handles all the logic for us, which means we only need to install the package and use the component by sending in the necessary values, and it handles the core logic for us.

The first step would be to install react-paginate package using the command below:

npm install react-paginate --save

Once that is successful, we can import and use it in this manner, and everything will be fine:

import React, { useState, useEffect } from 'react';
import { request } from 'graphql-request';
import ReactPaginate from 'react-paginate';
const App = () => {
const [blogPosts, setBlogPosts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(3);
// ...
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = blogPosts.slice(indexOfFirstPost, indexOfLastPost);
const paginate = ({ selected }) => {
setCurrentPage(selected + 1);
};
return (
<div className="container">
<div className="title">
<h1>Blog</h1>
</div>
{blogPosts ? (
<div className="blog-content-section">
{/* ... */}
<ReactPaginate
onPageChange={paginate}
pageCount={Math.ceil(blogPosts.length / postsPerPage)}
previousLabel={'Prev'}
nextLabel={'Next'}
containerClassName={'pagination'}
pageLinkClassName={'page-number'}
previousLinkClassName={'page-number'}
nextLinkClassName={'page-number'}
activeLinkClassName={'active'}
/>
</div>
) : (
<div className="loading">Loading...</div>
)}
</div>
);
};
export default App;
We also set the classes so that we can style the page navigation using the same CSS class names as before. You can check their [documentation](https://www.npmjs.com/package/react-paginate) to understand the package better and other available options available.

So far, we've seen how React implementation can be handled by retrieving all of our contents before using the splice method to show only the ones that match the currentPage set.

When working with real-world applications and fetching content from an API like Hygraph, it becomes a bad idea to get all data first as we did earlier because the site will slow down trying to get thousands of data while only a few will be shown to the users at once.

Hygraph makes it simple for us to handle content pagination by utilizing the following arguments:

ArgumentTypeDefinition
firstIntSeek forwards from the start of the result set.
lastIntSeek backward from the end of the result set.
skipIntSkip result set by given amount.
beforeStringSeek backward before specific ID.
afterStringSeeks forwards after specific ID.
These arguments can be passed into our [GraphQL query](/learn/graphql) to display specific data easily. For example, if we only want to see the first five data points from our [content API](/docs/api-reference), we can use the query below:
posts (first: 5) {
id
title
}

Let's now use some of these arguments to add pagination to our application. We will also use the relay specification to determine the pageSize (this is very important to calculate the pages and display page numbers as we have seen earlier). This is how our schema will now look with the useEffect() hook:

useEffect(() => {
const fetchBlogPosts = async () => {
const { posts, postsConnection } = await request(
'https://api-us-east-1.hygraph.com/v2/cl3zo5a7h1jq701xv8mfyffi4/master',
`
{
posts (first: ${postsPerPage}, skip: ${
currentPage * postsPerPage - postsPerPage
}) {
id
title
excert
postUrl
cover {
url
}
datePublished
author {
firstName
profilePicture {
url
}
}
}
postsConnection {
pageInfo {
pageSize
}
}
}
`
);
setBlogPosts(posts);
setTotalPosts(postsConnection.pageInfo.pageSize);
};
fetchBlogPosts();
}, [currentPage, postsPerPage]);

Looking at the schema, we can see that the first and skip arguments have been added to get the exact data for each page. We also use the relay specification to get the pageSize from our postsConnection object.

As we can see, we already saved the pageSize value to the state via this code setTotalPosts(postsConnection.pageInfo.pageSize).

All that remains is to ensure that the totalSize value is passed to the paginate component and that the condition set for the nextPage button is amended.

We can see the updated code for our App.js file that implements pagination with Hygraph and React in this GitHub Gist.

Conclusion

We learned how to implement pagination in our React application from scratch by handling all the logic and also using the react-paginate package in this guide. It is up to you to use any of the methods, but it is important to note that installing an extra package increases the size of your project and can also affect the load-time of your project, which may or may not be noticeable.

Blog Author

Joel Olawanle

Joel Olawanle

Joel Olawanle is a Frontend Engineer and Technical writer based in Nigeria who is interested in making the web accessible to everyone by always looking for ways to give back to the tech community. He has a love for community building and open source.

Share with others

Sign up for our newsletter!

Be the first to know about releases and industry news and insights.