React pagination tutorial: 4 ways with full code examples

Published on September 22, 2024

React pagination tutorial: 4 ways with full code examples

Pagination is the technique that splits up large amounts of data into pages for easier display and navigation in a website or app. It makes navigation convenient with buttons to go forward, go back, or jump to a specific page.

This tutorial shows you how to implement pagination in React in four different ways: client-side pagination, server-side pagination, using the React Paginate library, and using React Query (now known as Tanstack Query). The benefits of each solution are explained so you can choose the best React pagination solution for your project.

What is React pagination?

Even simple React apps may collect a lot of data that needs to be displayed in a user-friendly way. This raises two key challenges: 

  • Ensuring a good user experience: Users expect data to be well-organized and do not want to be overwhelmed with large amounts of data all displayed at once. 

  • Maintaining fast page load times: Rendering large datasets all at once can cause a big slowdown on your site or even cause your site to crash.

React pagination solves these problems by dividing data into chunks and displaying each chunk on a separate page while providing easy-to-use navigation buttons for the user. This makes data easier to navigate and consume for the user, and it breaks up the amount of data that needs to be loaded for each page, reducing network traffic and improving application efficiency.

Pagination navigation systems vary by design, but one well-known example of pagination navigation is on the Google search results page. Typical features of a page navigation system are “next” and “previous” buttons, and the ability to jump to specific pages.

Google search

Screenshot of the page navigation system on Google search.

How to implement React pagination?

There are four common ways to implement React pagination. Each method is fully detailed below to help you decide which one you'll want to use in your own projects.

For each part of this tutorial, you’ll need to create a React project and run it in your browser. You can do this by running these commands:

The examples in this article load data from a free-to-use REST API endpoint that returns fake blog posts, which is located at:

"https://jsonplaceholder.typicode.com/posts"

This can easily be swapped out for your own REST or GraphQL API.

All code examples from the below tutorials are available in full in our React pagination GitHub example repository, so you can clone it and try them all out for yourself.

Method #1: Vanilla React: client-side pagination

Client-side pagination means that the full dataset from the server is sent to the client (that is, the user's device) before being split up into pages on the client side. Below, we show an example of basic client-side pagination using vanilla React. 

Display all of the data on a single page

To start, call the example blog post API endpoint without any parameters: https://jsonplaceholder.typicode.com/posts. This will load all the posts (in this case, 100) that are available from the endpoint. Once you’ve confirmed that data is loading and displaying correctly, you can add pagination to your UI.

A note on state management in React: This code uses the useState and useEffect hooks, which are key parts of React’s built-in state management in React. Some people like to use Redux for state management, but for simplicity’s sake, we will use the built-in state management here.

It’s important to understand a little about state management in React in order to implement React pagination effectively. The “state” is a built-in React object that stores dynamic (changing) data within a component, and whenever the state changes, the component will re-render. With React pagination, you need to track the state of variables like currentPage and postsPerPage, as whenever these change, the page will need to be reloaded with the new content.

useState is the main hook for managing state in React. useEffect is used for dealing with any operations that happen outside of a React component — in this case, fetching data from an external API. In this example, once the data is fetched, it’s added to the posts variable via useState.

Add pagination logic

Now that the data is loaded, you can create a component for handling pagination on the client.

The above code uses the variables currentPage and postsPerPage to control pagination, so you’ll need to keep track of their state:

const [currentPage, setCurrentPage] = useState(1);

const [postsPerPage] = useState(10);

You'll also need to keep track of which posts need to be displayed for each page:

const indexOfLastPost = currentPage * postsPerPage;

const indexOfFirstPost = indexOfLastPost - postsPerPage;

const currentPosts = posts.slice(indexOfFirstPost, indexOfLastPost);

Update the PostList component to display only currentPosts, and add the Pagination component below it.

Add a loading indicator

Client side pagination is inefficient, as all data is downloaded during the initial page load. To make it clear to the user that data is indeed loading, you can keep track of the loading state and add a loading indicator to your page.

You can then add the loading indicator around your PostList component.

Add styling

There is no specific styling that's necessary for any of the pagination examples on this tutorial to work, but without some basic styling, they won't look very user friendly.

You'll probably want to eventually add your own styling to fit with your branding, but if you’re just working through these examples and haven’t set up your own stylesheets yet, you can copy our basic CSS styles from our GitHub into your own project.

View your paginated page

Run npm start to open your paginated page in the browser:

Pagination in action

A paginated page on a React app.

The full client-side pagination code for this tutorial is available on GitHub. 

Method #2: Vanilla React: server-side pagination

As mentioned earlier, client side-only pagination is very inefficient. Each time a page loads, the entire dataset must be loaded, and it might be very large. Downloading multiple megabytes (or more) of data will slow down page loads, and for very large datasets may even make your app unresponsive. 

Server-side pagination solves the problems inherent in client-side pagination. With server-side pagination, the data splitting occurs on the server. You pass pagination details to the API endpoint as query parameters, and it returns the specified subset of the data only. 

To implement server-side pagination, your API must support pagination parameters. Our example endpoint allows for this by taking query parameters like _page and _limit

https://jsonplaceholder.typicode.com/posts?_page=${currentPage}&_limit=${postsPerPage}

Just like with client-side pagination, you can use a useEffect hook to fetch your data. In this example, the previous endpoint has been modified to include pagination parameters. 

You’ll see that this code has been modified to fetch the next page of data whenever the current page changes — something that’s unnecessary for client-side pagination, as in that case all the data was loaded at once at the very beginning. This is done by adding [currentPage, postsPerPage] to the dependency array of useEffect.

You no longer need to calculate the currentPage, as this is now what’s returned by the paginated endpoint.

The full server-side pagination code for this tutorial is available on GitHub. 

Method #3: React-paginate library

The react-paginate library is specifically designed just for handling the UI side of pagination in React apps. This library contains more features than our vanilla React examples: It includes a dropdown for the user to choose how many items should be displayed per page, next and previous buttons, and selective displaying of page numbers when the number of pages becomes too large.

blog posts with react-paginate

The react-paginate library allows you to add an “items per page” selector box.

eveniet quod temporibus

The react-paginate library has Next and Previous buttons, as well as buttons for each individual page.

Page navigation bar

Clicking "..." buttons gives more page number options when there are too many pages to fit on the screen.

Install the react-paginate library

You can install the react-paginate library with one command: 

npm install react-paginate

Ensure that your API and react-paginate are using the same index

The react-paginate library is zero-indexed. This means it counts its first page as 0 and its second page as 1. However, the API we’re using in this example (jsonplaceholder.typicode.com) starts counting pages from 1. If the two don't match, this can lead to errors or blank content when you inadvertently try to retrieve page 0 from your endpoint, which in this case doesn’t exist!

A simple fix is to adjust your call to the API endpoint to always add 1 to your current page number:

Implement the ReactPaginate component

The ReactPaginate component can replace the custom Pagination component in the earlier examples.

View your paginated page

Run npm start to open your paginated page in the browser:

Blog posts with react-paginate

The full React Paginate code for this tutorial is available on GitHub.

Method #4: React Query pagination

React Query (now called TanStack Query) is a data-fetching and state management library. It handles state management and errors for you, so it’s easier to use than useState and useEffect. However, it only works with data that’s already been paginated on the server side, so you must ensure your API can handle these requests (if it accepts pagination query parameters, it does). 

Install React Query (TanStack Query)

Install React Query with this command: 

npm install @tanstack/react-query

Use useQuery to handle state management when fetching data

Use React Query’s useQuery hook (instead of React's useEffect) to fetch your blog post data. This means you no longer need to manage the state yourself as you fetch and load the data.

To stop your data from being cleared when new data is called, set keepPreviousData to true; otherwise, your page will briefly appear blank while data is loaded, which may confuse users. 

useQuery has fetchPosts as its query function. This means that when the ReactPaginate component loads and useQuery is called, this in turn calls fetchPosts, which is used to fetch the blog posts. You can destructure the queryKey variable to get the current page number for the endpoint. 

To make your React Query code work, you also need to wrap your app’s root component with QueryClientProvider, passing in the QueryClient

This is located in index.js in the provided example codebase:

Render the data and pagination buttons

Start by rendering your blog post list in the usual way.

To render the pagination buttons, you need to either write your own code or use the React Pagination library. For this example, use the React Paginate library, as it’s simpler to use and has a better user experience.

View your paginated page

You can now view your paginated page in the browser. It will look the same as in the React Paginate example, as we’re re-using that library in this example.

The full code for React Query pagination using TanStack is available on our GitHub.

React pagination best practices

Follow these best practices to get the most out of React pagination:

  1. Use server-side pagination to improve performance, particularly if your dataset is large.

  2. Use the react-paginate library to handle the UI. This will save time, since it's better to start with a simple library and then write custom code only if it can’t do what you want.

  3. Use React Query to handle your data fetching. It handles all your state management, reduces boilerplate, and automates your caching and error handling. The less code you write yourself, the less you need to debug!

  4. Thoroughly test any custom code: If you do decide to write your own pagination code, it must be really thoroughly tested, as small bugs in navigational components are incredibly frustrating for users. 

Pagination vs. infinite scroll

Infinite scroll is when content automatically loads as the user scrolls down the screen. It’s an alternative to pagination for displaying content in smaller chunks. 

Infinite scroll is very common in social media such as Instagram and TikTok, and it’s also used in certain types of ecommerce products where the user is encouraged to browse. If this is something you'd like to try, check out our guide to using lazy loading and suspense with infinite scroll in React.

Infinite scroll may be the shiny new kid on the block, but pagination is still the right choice for many types of websites, including:

  • Most ecommerce and online retail: The user has more control over navigation, which is useful when searching for something specific (or going back to find something they’ve decided they actually want).

  • Search engines and job listings pages: Pagination is preferred here because it avoids overwhelming the users with too much content.

  • Critical websites where accessibility or ease of navigation is important: This includes education, healthcare, legal, insurance, government, and library websites.

Keep the user in mind when choosing your frontend designs

When you have very large datasets, you need pagination to maintain good user experience and performance. The most important consideration when deciding how to paginate (or whether to use infinite scrolling), as well as how other frontend elements should behave, is your users. Think about what they want to accomplish while using your app and tailor it to their needs and expectations.

To ensure the rest of your application has good user experience and performance, you can use Contentful to store your content. Contentful is a scalable composable content platform with fast GraphQL and REST APIs that support server-side pagination and integrate seamlessly with React. Media assets are optimized for delivery to different devices, and content is delivered from our high-speed CDN.

Using Contentful allows you to build fast, reliable, user friendly applications and make large datasets much more digestible.

Subscribe for updates

Build better digital experiences with Contentful updates direct to your inbox.

David Fateh

David Fateh

Software Engineer, Contentful

David Fateh is a software engineer with a penchant for web development. He helped build the Contentful App Framework and now works with developers that want to take advantage of it.

Related articles

What's TypeScript? It's a powerful tool for frontend & backend developers. It promotes better code quality and protects against common JavaScript mistakes.
Guides

What is TypeScript and why should you use it?

August 18, 2022

Contentful is a powerful tool for game studios to efficiently create game content across many disparate teams. See how it's done with Unity in this tutorial.
Guides

Unity game development with Contentful

February 1, 2024

This comprehensive guide explores the key differences and possibilities of Rust and TypeScript and explains their type systems and error-handling capabilities.
Guides

Rust and TypeScript: A comprehensive guide to their differences and integration

December 4, 2024

Contentful Logo 2.5 Dark

Ready to start building?

Put everything you learned into action. Create and publish your content with Contentful — no credit card required.

Get started