How to implement pagination in ReactJS

Key takeaways:

  • Pagination reduces loading time and improves user experience by dividing large datasets into smaller, manageable pages.

  • React’s functional components with Hooks like useState and useEffect simplify pagination management. These Hooks allow developers to manage the page state and perform data fetching when components mount.

  • Developers can use axios or fetch to retrieve data from APIs or local JSON files.

  • Server-side pagination is recommended for large datasets. It minimizes data transfer between the client and server, improving performance, especially when dealing with APIs that return large datasets.

Client-side pagination is a common method used in web development—on the client side—to divide large data sets into smaller, more manageable chunks. This approach ensures that the entire dataset is loaded on the client side, with only a subset displayed at any given time. Client-side pagination functionality can be easily and effectively implemented using React, a well-known JavaScript library for creating user interfaces.

Pagination
Pagination

We’ll learn to fetch and display data in a paginated format. Add pagination controls to navigate through pages and also manage state to keep track of the current page and handle user interactions.

Implementation

We’ll explore how to implement pagination in a React application by following a few steps, using a simple example. All of our components, as well as the logic for obtaining the data and segmenting it for pagination, will be stored in App.jsx file.

import React from 'react' // Importing the React library
function App() { // Defining the App component
return (
<div>
</div>
);
}
export default App; // Exporting the App component as the default export

Now, add the following two states to the App component.

const [data, setData] = useState([]) // Initialize 'data' state with an empty array []
const [loading, setLoading] = useState(true); // Initialize 'loading' state with the value 'true', indicating that data is still loading
  • The data state will store the actual data fetched using Axios.

  • The loading state indicates whether the data has been fetched or not.

Fetching data using Axios

Axios is a JavaScript library often used to make HTTP calls. Its API is easy to use and supports modern browsers.

First, to set up Axios, run the following command in the root directory of your project.

npm install axios
Install axios to fetch data

Next, import Axios at the top of the component file where you want to make the request to fetch data.

import axios from 'axios';
Import axios

Then, create a JSON file named data.json with the following sample data:

[
{
"id": 1,
"first_name": "Humfrid",
"last_name": "Larwood",
"city": "Tatebayashi"
},
{
"id": 2,
"first_name": "Darlleen",
"last_name": "Peegrem",
"city": "Tianning"
},
{
"id": 3,
"first_name": "Alana",
"last_name": "Francisco",
"city": "Orléans"
},
{
"id": 4,
"first_name": "Glyn",
"last_name": "Littledike",
"city": "Rio Real"
},
{
"id": 5,
"first_name": "Camile",
"last_name": "Cano",
"city": "Tampa"
},
{
"id": 6,
"first_name": "Ara",
"last_name": "Oneil",
"city": "Calçada"
},
{
"id": 7,
"first_name": "Kimberlyn",
"last_name": "Gilfoy",
"city": "Suresnes"
},
{
"id": 8,
"first_name": "Magdalene",
"last_name": "Kunzel",
"city": "Kalloní"
},
{
"id": 9,
"first_name": "Rosalyn",
"last_name": "Dobsons",
"city": "Nakło nad Notecią"
},
{
"id": 10,
"first_name": "Kiley",
"last_name": "Rimes",
"city": "Nanxi"
},
{
"id": 11,
"first_name": "Lenette",
"last_name": "Brokenshaw",
"city": "Lộc Bình"
},
{
"id": 12,
"first_name": "Julie",
"last_name": "Origin",
"city": "Yanshou"
},
{
"id": 13,
"first_name": "Carr",
"last_name": "Haug",
"city": "Fujikawaguchiko"
},
{
"id": 14,
"first_name": "Duke",
"last_name": "Kuhnel",
"city": "Nyima"
},
{
"id": 15,
"first_name": "Yulma",
"last_name": "Stanwix",
"city": "Río Alejandro"
},
{
"id": 16,
"first_name": "Mei",
"last_name": "Keith",
"city": "Tucson"
},
{
"id": 17,
"first_name": "Scotty",
"last_name": "Suggitt",
"city": "Sukamenak"
},
{
"id": 18,
"first_name": "Mirelle",
"last_name": "Howbrook",
"city": "Viçosa do Ceará"
},
{
"id": 19,
"first_name": "Debor",
"last_name": "Foad",
"city": "Kendayakan"
},
{
"id": 20,
"first_name": "Cross",
"last_name": "Karppi",
"city": "Mozdok"
},
{
"id": 21,
"first_name": "Byrle",
"last_name": "Byles",
"city": "Rybno"
},
{
"id": 22,
"first_name": "York",
"last_name": "Grime",
"city": "Shaxi"
},
{
"id": 23,
"first_name": "Findlay",
"last_name": "Vettore",
"city": "Радовиш"
},
{
"id": 24,
"first_name": "Consolata",
"last_name": "Selwin",
"city": "Ban Houayxay"
},
{
"id": 25,
"first_name": "Shannah",
"last_name": "Spivie",
"city": "Şuraabad"
},
{
"id": 26,
"first_name": "Tyrone",
"last_name": "Westnedge",
"city": "Lendan"
},
{
"id": 27,
"first_name": "Glen",
"last_name": "Kaysor",
"city": "Jincheng"
},
{
"id": 28,
"first_name": "Malissia",
"last_name": "Alfwy",
"city": "Cartagena"
},
{
"id": 29,
"first_name": "Alice",
"last_name": "Ondrousek",
"city": "Potrero Grande"
},
{
"id": 30,
"first_name": "Rossy",
"last_name": "Mosley",
"city": "Daşoguz"
},
{
"id": 31,
"first_name": "Tybi",
"last_name": "Scoines",
"city": "Paris 12"
},
{
"id": 32,
"first_name": "Jerrome",
"last_name": "Setchfield",
"city": "Kalidawir"
},
{
"id": 33,
"first_name": "Ginelle",
"last_name": "Ickovicz",
"city": "Wamba"
},
{
"id": 34,
"first_name": "Bendicty",
"last_name": "Morigan",
"city": "Malinta"
},
{
"id": 35,
"first_name": "Susie",
"last_name": "Allwright",
"city": "Penisihan"
},
{
"id": 36,
"first_name": "Bonnie",
"last_name": "Hullin",
"city": "Hradec Králové"
},
{
"id": 37,
"first_name": "Lawry",
"last_name": "Kynsey",
"city": "Podporozh’ye"
},
{
"id": 38,
"first_name": "Kakalina",
"last_name": "Thornborrow",
"city": "Plalar"
},
{
"id": 39,
"first_name": "Floria",
"last_name": "Chaudret",
"city": "Lanlongkou"
},
{
"id": 40,
"first_name": "Alexa",
"last_name": "Flaubert",
"city": "Borlänge"
},
{
"id": 41,
"first_name": "Rhodia",
"last_name": "Dacks",
"city": "Rurrenabaque"
},
{
"id": 42,
"first_name": "Ignace",
"last_name": "Lortzing",
"city": "Gandusari"
},
{
"id": 43,
"first_name": "Trude",
"last_name": "Trobey",
"city": "Tuchengzi"
},
{
"id": 44,
"first_name": "Dahlia",
"last_name": "Mompesson",
"city": "Sarreguemines"
},
{
"id": 45,
"first_name": "Lynnet",
"last_name": "Densham",
"city": "Yangzi"
},
{
"id": 46,
"first_name": "Alley",
"last_name": "Coetzee",
"city": "Ulyanovsk"
},
{
"id": 47,
"first_name": "Loren",
"last_name": "Fouracre",
"city": "Shahbā"
},
{
"id": 48,
"first_name": "Lamont",
"last_name": "Boutflour",
"city": "Bāndarban"
},
{
"id": 49,
"first_name": "Norby",
"last_name": "O'Cuddie",
"city": "Libas"
},
{
"id": 50,
"first_name": "Devon",
"last_name": "Edson",
"city": "Chelyabinsk"
},
{
"id": 51,
"first_name": "Francoise",
"last_name": "Bevan",
"city": "Calapan"
},
{
"id": 52,
"first_name": "Lettie",
"last_name": "Demcik",
"city": "Kresna"
},
{
"id": 53,
"first_name": "Camila",
"last_name": "Capin",
"city": "Moutsamoudou"
},
{
"id": 54,
"first_name": "Cammy",
"last_name": "Feldberg",
"city": "Guang’an"
},
{
"id": 55,
"first_name": "Juliette",
"last_name": "Braunstein",
"city": "Yucheng"
},
{
"id": 56,
"first_name": "Norman",
"last_name": "Spread",
"city": "Angol"
},
{
"id": 57,
"first_name": "Sheela",
"last_name": "Sellstrom",
"city": "Hongtu"
},
{
"id": 58,
"first_name": "Angelia",
"last_name": "Desbrow",
"city": "Santa Elena"
},
{
"id": 59,
"first_name": "Frederica",
"last_name": "Fulleylove",
"city": "Hengli"
},
{
"id": 60,
"first_name": "Seana",
"last_name": "Leither",
"city": "Samparna"
},
{
"id": 61,
"first_name": "Aleen",
"last_name": "Springtorpe",
"city": "Xiaoweizhai"
},
{
"id": 62,
"first_name": "Desiri",
"last_name": "De Bernardi",
"city": "San Diego"
},
{
"id": 63,
"first_name": "Karim",
"last_name": "Bloor",
"city": "Hövsan"
},
{
"id": 64,
"first_name": "Jennifer",
"last_name": "Hallgate",
"city": "Wantou"
},
{
"id": 65,
"first_name": "Dawn",
"last_name": "Soars",
"city": "Wilkołaz"
},
{
"id": 66,
"first_name": "Thomasa",
"last_name": "Beardsworth",
"city": "Norrköping"
},
{
"id": 67,
"first_name": "Brennen",
"last_name": "Brenton",
"city": "Yongshan"
},
{
"id": 68,
"first_name": "Wini",
"last_name": "Ackerley",
"city": "Korolevo"
},
{
"id": 69,
"first_name": "Mufinella",
"last_name": "Nevins",
"city": "Qimantage"
},
{
"id": 70,
"first_name": "Pammi",
"last_name": "Dollin",
"city": "Glad"
},
{
"id": 71,
"first_name": "Royce",
"last_name": "Friedlos",
"city": "Konso"
},
{
"id": 72,
"first_name": "Xever",
"last_name": "Fanthom",
"city": "Monte"
},
{
"id": 73,
"first_name": "Becki",
"last_name": "Kiddie",
"city": "Skópelos"
},
{
"id": 74,
"first_name": "Joscelin",
"last_name": "Holleworth",
"city": "Jataí"
},
{
"id": 75,
"first_name": "Skipper",
"last_name": "Crepin",
"city": "Alasmalang"
},
{
"id": 76,
"first_name": "Nelle",
"last_name": "Lazenby",
"city": "Koronowo"
},
{
"id": 77,
"first_name": "Babbette",
"last_name": "Bickerstaffe",
"city": "Potrero Grande"
},
{
"id": 78,
"first_name": "Ricca",
"last_name": "Carcas",
"city": "Bhairab Bāzār"
},
{
"id": 79,
"first_name": "Zarla",
"last_name": "Po",
"city": "Le Port"
},
{
"id": 80,
"first_name": "Mil",
"last_name": "Lowers",
"city": "Telangi Satu"
},
{
"id": 81,
"first_name": "Douglas",
"last_name": "Pee",
"city": "Itsukaichi"
},
{
"id": 82,
"first_name": "Eden",
"last_name": "Mirando",
"city": "São Paio de Gramaços"
},
{
"id": 83,
"first_name": "Nefen",
"last_name": "Achrameev",
"city": "Yuqunweng"
},
{
"id": 84,
"first_name": "Erroll",
"last_name": "Melmore",
"city": "Qingxi"
},
{
"id": 85,
"first_name": "Dehlia",
"last_name": "Hinkensen",
"city": "Chichibu"
},
{
"id": 86,
"first_name": "Mathilde",
"last_name": "Kleimt",
"city": "Tarbes"
},
{
"id": 87,
"first_name": "Henka",
"last_name": "O'Sherrin",
"city": "Huatan"
},
{
"id": 88,
"first_name": "Dieter",
"last_name": "Hanne",
"city": "Kanbe"
},
{
"id": 89,
"first_name": "Bentlee",
"last_name": "Pooley",
"city": "Tauca"
},
{
"id": 90,
"first_name": "Emogene",
"last_name": "Henstridge",
"city": "Saint-Amand-les-Eaux"
},
{
"id": 91,
"first_name": "Thatcher",
"last_name": "Poyle",
"city": "Gobernador Ingeniero Valentín Virasoro"
},
{
"id": 92,
"first_name": "Heindrick",
"last_name": "Tondeur",
"city": "Pecatu"
},
{
"id": 93,
"first_name": "Gal",
"last_name": "Dyster",
"city": "Al Bājūr"
},
{
"id": 94,
"first_name": "Tova",
"last_name": "Alcock",
"city": "Yantal’"
},
{
"id": 95,
"first_name": "Sosanna",
"last_name": "Rief",
"city": "Cagwait"
},
{
"id": 96,
"first_name": "Mirna",
"last_name": "Brooksbie",
"city": "Dayuanhuizu"
},
{
"id": 97,
"first_name": "Ethelin",
"last_name": "Buckner",
"city": "Wellington"
},
{
"id": 98,
"first_name": "Gael",
"last_name": "Poulsum",
"city": "Ryczywół"
},
{
"id": 99,
"first_name": "Libby",
"last_name": "Harbottle",
"city": "Leskovac"
},
{
"id": 100,
"first_name": "Leda",
"last_name": "Bugler",
"city": "Pak Phanang"
}
]
data.json

Fetch this data inside the useEffect Hook using the get method. It accepts a URL as an input and performs a get request on it.

// useEffect Hook to fetch data from 'data.json' using axios when the component mounts
useEffect(() => {
axios.get('data.json')
.then(res => {
setData(res.data);
setLoading(false);
})
.catch(() => { // Error handling: Displays an alert if there's an error fetching data
alert('There was an error while retrieving the data')
})
}, [])

Explanation

  • Line 2: Use the useEffect Hook to ensure that the data fetching process is initiated when the component loads.

  • Lines 4–7: After receiving the full response, perform the subsequent actions inside the then block of the axios.get() function.

  • Line 5: Update the data state with the received data from the response.

  • Line 6: Set the loading state to false to indicate that the data fetching process is complete.

  • Lines 8–10: Implement the catch block to handle errors that may occur during the data fetching process.

Want to get a hands-on experice with Axios? Try this project: Build an Online Video Player in React and YouTube Data API.

Display the data

Create a component where you want to display the fetched data. In this example, we will display our data in the form of a table in a Records component.

Explanation

  • Line 1: The data prop is used to populate the table rows dynamically based on the provided data.

  • Line 13: The data.map() function is used to iterate over each item in the data array.

  • Lines 14–19: For each item, a new table row (<tr>) is created, and the relevant data fields (ID, first name, last name, city) are displayed in separate table cells (<td>).

Implementing pagination logic

So far, the table displays all records on a single page, which can be overwhelming for users. To address this issue, pagination has been introduced as a solution. We will be working on the App component.

Deciding the states

We have the flexibility to determine the number of records to be displayed on each page. This choice influences the calculation of the total number of pages. Additionally, tracking the current page number that the user is viewing is crucial.

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

Displaying records on each page

To determine the range of records to be displayed on the current page, we can calculate the index of the last record and the index of the first record. The index of the last record is obtained by multiplying the current page number, currentPage, by the number of records per page, recordsPerPage.

Similarly, the index of the first record can be obtained by subtracting the number of records per page, recordsPerPage, from the index of the last record, endIndex.

const endIndex = currentPage * recordsPerPage;
const indexOfFirstRecord = endIndex - recordsPerPage;

Get hands-on practice with React with a real-world application with this project: Build the Frontend of a Financial Application Using React.

Extracting records for the current page

To display the records on the current page, we can use the slice function in JavaScript. By specifying the range of indexes using indexOfFirstRecord and endIndex, we can extract the subset of records from the data array.

Note: The slice function's end index is exclusive, so the range from endIndex to endIndex - 1 will be selected.

// Records to be displayed on the current page
const currentRecords = data.slice(indexOfFirstRecord, endIndex);

The resulting subset of records is then passed as props to the Records component, which will render the table with the records specific to the current page.

Determining the number of pages

To calculate the number of pages needed to display all the records, we can use the Math.ceil function in JavaScript. By dividing the total number of records, data.length, by the desired number of records per page, recordsPerPage, we will obtain the quotient.

If any remaining records cannot fit completely on a page, the Math.ceil function ensures that an additional page is allocated to accommodate them. The resulting value is assigned to the nPages variable, representing the total number of pages in the pagination.

const nPages = Math.ceil(data.length / recordsPerPage)

Dynamic Pagination component

To implement pagination, create a new component called Pagination. The necessary variables, nPages, currentPage, and setCurrentPage, are passed to the pagination component as props.

function Pagination({ nPages, currentPage, setCurrentPage }) {
return (
<div>
Pagination Component
</div>
)
}
Pagination component

Within the pagination component, generate an array containing all the page numbers from 1 to nPages. This can be accomplished by utilizing the spread operator (...) along with the Array and keys() methods. To exclude the first element, 0, from the array, use the slice method.

const pageNumbers = [...Array(nPages + 1).keys()].slice(1);

The pageNumbers array will be a reference for rendering the pagination buttons or links, allowing users to navigate between pages.

Next, create a container to display the page numbers and page controls. This container will hold the pagination buttons or links that allow users to navigate between pages.

When a page number is clicked, it triggers an event that sets the current page to that specific number. As a result, the table dynamically displays the records corresponding to the selected page.

To enable navigation between pages, implement the goToPrevPage and goToNextPage functions.

The goToPrevPage function

  1. Create the goToPrevPage function to decrease the current page number by 1.

  2. Add a condition to check if the current page is not the first page, currentPage !== 1.

  3. If the condition is met, update the current page by calling setCurrentPage with the decremented value, setCurrentPage(currentPage - 1).

const goToPrevPage = () => {
if (currentPage !== 1) setCurrentPage(currentPage - 1);
};
goToPrevPage function

The goToNextPage function

  1. Create the goToNextPage function to increase the current page number by 1.

  2. Add a condition to check if the current page is not the last page, currentPage !== nPages.

  3. If the condition is met, update the current page by calling setCurrentPage with the incremented value setCurrentPage(currentPage + 1).

const goToNextPage = () => {
if (currentPage !== nPages) setCurrentPage(currentPage + 1);
};
goToNextPage function

These functions ensure that the current page is within the valid range and update the state accordingly to reflect the new page.

Code example

Let’s look at a working example by combining everything and executing the application.

import React, { useState, useEffect } from 'react'
import axios from 'axios'
import Records from './components/Records';
import Pagination from './components/Pagination';

function App() {
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(true);
  const [currentPage, setCurrentPage] = useState(1);
  const [recordsPerPage] = useState(10);

  useEffect(() => {
    axios.get('data.json')
      .then(res => {
        setData(res.data);
        setLoading(false);
      })
      .catch(() => {
        alert('There was an error while retrieving the data')
      })
  }, [])

  const endIndex = currentPage * recordsPerPage;
  const indexOfFirstRecord = endIndex - recordsPerPage;
  const currentRecords = data.slice(indexOfFirstRecord, endIndex);
  const nPages = Math.ceil(data.length / recordsPerPage)

  return (
    <div className='container mt-5'>
      <h2> Simple Pagination Example in React </h2>
      <Records data={currentRecords}/>
      
      <Pagination
        nPages={nPages}
        currentPage={currentPage}
        setCurrentPage={setCurrentPage}
      />
    </div>
  );
}

export default App;
Code implementation

We successfully implemented pagination in a React application. We utilized React hooks, such as useState and useEffect, to manage the state and handle asynchronous data fetching. By dividing the data into smaller chunks and displaying a limited number of records on each page, we greatly improved the user experience and performance of the application.

Continue learning more about React

Explore these projects for hands-on practice for creating real-world applications with React:

Frequently asked questions

Haven’t found what you were looking for? Contact Us


What is pagination in React.js?

Pagination refers to dividing large datasets into smaller pages, enhancing performance and navigation.


How do you implement server-side pagination in React?

Server-side pagination involves fetching only the required records from the server for each page request. Tools like React Query or Axios can be integrated for this purpose.


Should I use client-side or server-side pagination?

Use client-side pagination for small datasets and server-side pagination for larger or dynamic data sources to reduce loading times.


Free Resources

Copyright ©2025 Educative, Inc. All rights reserved