Sampath Gajawada I'm a full-stack developer who always wishes to implement new and challenging elements in my daily life. Currently, my focus is on React and Vue.

React pagination from scratch using Hooks

6 min read 1882

React Pagination Scratch

Applications that display large amounts of data in the form of either a list or table can implement pagination to divide the data into a sequence of pages. Instead of fetching huge amounts of data from a server API, pagination loads data on demand, decreasing load time and improving the overall UI.

While there are many libraries available for implementing pagination, some are a bit heavy, harming your app’s overall performance. In this article, we’ll create a reusable pagination component using React Hooks.

As an example, we’ll display the passenger list in the image below below using pagination. The complete code is available at the react-pagination GitHub repository; please feel free to fork it:

Pagination Scratch React Hooks

Table of contents

Before jumping into the implementation, let me introduce two React Hooks that we’ll use in this article. If you’re already familiar with React Hooks, feel free to skip to the tutorial.

useState Hook

If you’d like to add state to your functional component, the useState Hook will do the job. Consider the example below:

// a new state variable called "currentPage"  
const [currentPage, setCurrentPage] = useState(1);

The useState Hook helps to preserve the values between function calls. It takes a single argument, which is the initial state. In the example above, the currentPage variable is set to 1 by default. The useState Hook will return a pair of values, the first being the variable’s state, and the second is the function to set the state.

We’ll use the useState Hook to maintain the state for the current page, the minimum and maximum page limits, as well as hold the response data from the passenger API.

useEffect Hook

If you’d like to perform any side effects like data fetching, DOM manipulation, or subscribing and unsubscribing to events, the useEffect Hook will help you:

useEffect(()=>{
  // call rest api
},[]);

React has access to the state of your component, so it will essentially remember the function you passed and invoke it after DOM updates. We use the useEffect Hook to fetch passenger data from the REST API.



Setting up the project

Our goal is to display the passenger list with pagination, so we’ll use the free REST API to provide a list of passenger data. Begin by creating a new React application using the Create React App command below:

npx create-react-app pagination-tutorial

Remove all the default content from the App.js file. We’ll include our components here later. The final file should look like the code below:

import './App.css';
function App() {
  return (
    <div className="App">
       <div className="App-header">
           // include the Passenger componnet, which we create by end of the article
        </div>
    </div>
  );
}
export default App;

Now, we can run the application with the command below:

yarn start

Create a pagination component

The functional component allows you to split the UI into independent and reusable pieces. We’ll use a functional component that has a single argument holding props to the component. Before creating our pagination component, let’s clarify a few of the few terms used throughout the rest of the article.

  • Current page: represents the user position on the pager
  • Max page limit: represents the upper limit on the pager
  • Min page limit: represents the lower limit on the pager

Jump to the src folder and create a new file called Pagination.js:

import React from 'react';
const Pagination = (props)=>{
    return(
        <div>
        </div>
    )
}
export default Pagination;

Since we’re creating a reusable component for pagination, assume that the component gets all inputs from the parent component using props. We aim to build an output like the screenshot below:

Final Passenger List View

Initialize the terms we discussed in previous section:

// init
  const { currentPage, maxPageLimit, minPageLimit} = props;
  const totalPages = props.response.totalPages-1;
  const data = props.response.data;

Now, to input the total number of pages, we’ll build page numbers starting from 1. The limit will go based on maximum and minimum page limits:

Build Page Numbers Max Min

Build the pages array, which will hold all the numbers ranging from 1 to total pages:

 // build page numbers list based on total number of pages
    const pages = [];
    for(let i=1 ; i<=totalPages; i++){
        pages.push(i);
    }

To create the UI for all page numbers that fall within the maximum and minimum page limits, iterate through the pages array built with the step above. If the iteration page matches with the current page, make it active:

const pageNumbers = pages.map(page => {
        if(page <= maxPageLimit  && page > minPageLimit) {
            return(
        <li key={page} id={page} onClick={handlePageClick} 
            className={currentPage===page ? 'active' : null}>
            {page}
        </li>
            );
        }else{
            return null;
        }
    }

 );

Next, we’ll create ellipsis dots , which appear when there are more pages than the current page on either the left or right. On clicking the ellipsis, the user can move to either the previous or the next group of pages. You can see these in the image below, highlighted in green:

Create Page Ellipses Max Min

  // page ellipses
    let pageIncrementEllipses = null;
    if(pages.length > maxPageLimit){
        pageIncrementEllipses = <li onClick={handleNextClick}>&hellip;</li>
    }
    let pageDecremenEllipses = null;
    if(minPageLimit >=1){
        pageDecremenEllipses = <li onClick={handlePrevClick}>&hellip;</li> 
    }
const renderData = (data)=>{

Let’s render our passengers in a list format. In the code below, I’m showing the ID of the passenger and the name of the airline used by each passenger:

    return(
        <ul>
            {data.map((d)=> 
            <li key={d['_id']}> The passenger having id {d['_id'].slice(d['_id'].length-5)} using {d.airline[0].name} airlines</li>)
            }
        </ul>
    )
}

Next, we’ll combine all of the UI elements and return them in our functional component along with the Prev and Next buttons.

On clicking Prev, the user navigates to the previous page. On clicking Next, the user navigates to the next page. The UI elements are aligned as follows:

1. Passenger’s data
2. Pager
    1. Prev button
    2. Page decrement ellipses
    3. Page numbers with current page selection
    4. Page increment ellipses
    5. Next button

 return (
        <div className="main">
            <div className="mainData">
              {renderData(data)}
            </div>
            <ul className="pageNumbers"> 
               <li>
                   <button onClick={handlePrevClick} disabled={currentPage === pages[0]}>Prev</button>
               </li>
               {pageDecremenEllipses}
                {pageNumbers}
               {pageIncrementEllipses}
                <li>
                   <button onClick={handleNextClick} disabled={currentPage === pages[pages.length-1]}&gt;Next</button>
               </li>
            </ul>
        </div>
    )

So far, we created the UI, which includes events like page click, prev click, and next click. Now, let’s bind all the events:

 const handlePrevClick = ()=>{
        props.onPrevClick();
    }
    const handleNextClick = ()=>{
        props.onNextClick();
    }
    const handlePageClick = (e)=>{
        props.onPageChange(Number(e.target.id));
    }

Using the pagination component

Now, we’ll navigate to the parent component, which is the Passengers component in our case, responsible for fetching passenger data and setting the pagination props. Use the Pagination component created above in this component.

To create the Passengers component, first, initialize the state. You can refer to the state variables below. In simple terms, we’ll show page numbers starting from 1 to 5 at the start of our pagination component. We initialize the minimum and maximum page limits with 0 and 5, respectively.


More great articles from LogRocket:


We set the current page to 1, meaning that the user will be on the first page. By setting the passenger data to an empty array, we determine that we’ll fetch the data in the next steps. The page number limit, 5, tells us that we’ll show only five page numbers on UI:

  const pageNumberLimit = 5;
  const [passengersData, setData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [currentPage, setCurrentPage] = useState(1);
  const [maxPageLimit, setMaxPageLimit] = useState(5);
  const [minPageLimit, setMinPageLimit] = useState(0);

When the current page changes, we’ll fetch the passenger data from our API with the useEffect Hook, persisting it in the passengersData state variable:

 useEffect(()=>{
    setLoading(true);
    fetch(`https://api.instantwebtools.net/v1/passenger?currentPage=${currentPage}&size=5`)
    .then((response) => response.json())
    .then((json) => { setData(json); setLoading(false);});

  },[currentPage]);

The core step of this complete pagination is updating the maximum, minimum, and current page, which are updated on the page change, previous click, and next click.

Previous click

Assume you are on the sixth page of our pagination component. If you click the Prev button, you’ll be directed to the fifth page, and the page limits should be reset to 1 and 5.

To achieve the example above, we’ll update the maximum and minimum page limits only if the previous page is not in the current page range. The remainder obtained after dividing the previous page with page number should be zero.

Establish Previous Click

For example, consider that the maximum page limit should equal the current max limit minus the page number limit. In our example in the picture above, 10 minus five equals five.

Establish Previous Click After

For the minimum page limit, we subtract the page number limit from the current minimum limit. In our example, we calculate that six minus five equals one. Keep in mind that the current page is the previous page minus 1.

Next click

We’ll explore the next click as a toggle of the previous example. Assume you’re on the fifth page now. Click Next, and you should navigate to the sixth page. The page limits should be between 6 and 10.

Like with the previous click, we can only achieve the scenario above by updating the maximum and minimum page limits. Maximum and minimum page limits are updated only when the next page is greater than the current max page limit:

Establish Next Click

To calculate the maximum page limit, we add the current maximum limit to the page number limit. In our example, we add five and five to equal 10.

After Next Click

The minimum page limit is the current minimum limit plus the page number limit. In our example, one plus five equals six. The current page is the previous page plus one.

Page change

On click of any page number, update the current page to the specific number. Below is the code snippet for all major events:

const onPageChange= (pageNumber)=>{
    setCurrentPage(pageNumber);
  }
  const onPrevClick = ()=>{
      if((currentPage-1) % pageNumberLimit === 0){
          setMaxPageLimit(maxPageLimit - pageNumberLimit);
          setMinPageLimit(minPageLimit - pageNumberLimit);
      }
      setCurrentPage(prev=> prev-1);
   }

  const onNextClick = ()=>{
       if(currentPage+1 > maxPageLimit){
           setMaxPageLimit(maxPageLimit + pageNumberLimit);
           setMinPageLimit(minPageLimit + pageNumberLimit);
       }
       setCurrentPage(prev=>prev+1);
    }

To create the UI for passengers with pagination, we’ll combine all the attributes needed and send them as props to the pagination component:

const paginationAttributes = {
    currentPage,
    maxPageLimit,
    minPageLimit,
    response: passengersData,
  };
  return(
    <div>
        <h2>Passenger List</h2>
        {!loading ? <Pagination {...paginationAttributes} 
                          onPrevClick={onPrevClick} 
                          onNextClick={onNextClick}
                          onPageChange={onPageChange}/>
        : <div> Loading... </div>
        }
    </div>
)

Now, we’ve built both parent and child components. You are ready to use the parent component anywhere in the application.

Wrapping up

In this tutorial, we created a pagination component by sending the current page, maximum page limit, minimum page limit, and other page events as props to the component. We used React Hooks for state management and fetching data.

While we developed our example application in React, you can apply the same logic to any framework. I hope you learned something new! Be sure to leave a comment if you have any questions. Happy coding!

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Sampath Gajawada I'm a full-stack developer who always wishes to implement new and challenging elements in my daily life. Currently, my focus is on React and Vue.

Leave a Reply