How to add a simple pagination to React.js App

mikegajdos

MikeGajdos

Posted on November 15, 2021

How to add a simple pagination to React.js App

In this little guide I will try to help you understand the essential concepts of pagination and how to implement it in React.js.

Often times, the web applications can not show all the data they need to their users at once. One of the reasons is the fact that rendering all the data at once can cause the webpage to slow down considerably.
If we want to optimize the performance we can adopt various techniques to render the data in a more efficient way. Some of these methods include infinite scroll with virtualization and pagination.

Pagination works well when you know the size of the dataset in advance, and you do not frequently alter it by adding new or deleting existing data.

The important thing to note is the pagination is usually implemented in coordination with the server-side code that allows the client applications to request the data in the form of "groups".

However, in this guide, we will implement the client-side pagination. Pagination is essentially just a process of "slicing" dataset into discrete pages to identify the sequential order for easier navigation and better user experience.

First we need to decide: how many items(contentPerPage) in the dataset do you want to display on each " page". It will depend on your desired UI.

Let's say the dataset has 9 items and you would like to display 3 items at a time (per page).

Calculating total pages is rather simple :

const totalPages = dataset.length / contentPerPage
// totalPages = 3
Enter fullscreen mode Exit fullscreen mode

This variable will give you a total number of pages based on the size of the dataset and your desired number of content per individual page.

Calculating the content per page is rather easy, but how do we display certain content based on what page(currentPage) we are on?

Array.slice( ) method to the rescue!

The slice() method returns a shallow copy or a portion of an array into a new array object selected from start to end (it is important to note the end is not included) where start and end represent the index of items in that array. The original array will not be modified.

const footballClubs = ["liverpool","chelsea", "manUtd","arsenal", "manCity", "spurs", "westHam", "everton", "cardiff"];
footballClubs.slice(2,5)
// output: [ "manUtd","arsenal", "manCity"]
footballClubs.slice(1,3)
// output : ["chelsea", "manUtd"]
Enter fullscreen mode Exit fullscreen mode

So now that we have an understanding of the slice method under the hood,all that we need to do is to use the currentPage ( the page we are on) value to slice the data which means getting startIndex and lastIndex based on the value of currentPage.

Important things to note:

  • Arrays are zero-based indexed
  • Pages in pagination will start from 1. ( no pagination should start with 0)

Here is the code that does what we've just described:


// initial load . Should start with 1st page 
const page = 1;
// our desired amount of contenct we would like to display
const contentPerPage = 3
const lastIndex = page * contentPerPage // 3
const firstIndex = lastIndex - contentPerPage // 0

footballClubs.slice(firstIndex, lastIndex)
// footballClubs.slice(0, 3) => ["liverpool","chelsea", "manUtd" ]

// page 2
// footballClubs.slice(3, 6) => [ "arsenal", "manCity", "spurs" ]

Enter fullscreen mode Exit fullscreen mode

Now that we've learned the concept behind Pagination, let's implement this in React

I would like to reiterate this is just the most basic implementation of pagination. ( if you would like to learn about the implementation of custom Pagination Component with previous , next buttons see my other article on the matter)

The most basic implementation of pagination in React is all about "reacting"(pun intended) to change of currentPage state variable. (on the initial load it will be set to 1 we explain why above).

Whenever the currentPage variable changes it will cause a re-render of the Components whose render method depends on that state variable and display "sliced" data based on its value.

We will provide our pagination buttons with onClick handlers to change the state of currentPage based on the page number.

This is an example of the concept we covered in practice πŸ‘‡

Pagination in React App demo version

I broke the pagination concept down into two separate components for sake of simplicity.

<CharacterGrid> component takes care of displaying the content based on currentPage and <Pagination> component takes care of page buttons and changing currentPage state variable. It is as mentioned above very simple implementation.

I used the πŸ’₯ emoji as an indicator for stuff related to the pagination. All the other code which is not related to Pagination is not explained ( assuming that you got data Fetching and other React main concepts covered)

This is how our top-level App Component looks likeπŸ‘‡


//App.js
import React, { useState, useEffect } from "react";
import "./App.css";
import Header from "./components/UI/Header";
import CharacterGrid from "./components/characters/CharacterGrid";
import Search from "./components/UI/Search";
import Pagination from "./components/pagination/Pagination";
import Spinner from "./components/UI/Spinner";
//πŸ‘‡ my custom data fetch hook
import { useDataFetch } from "./useDataFetch";

const App = () => {
  const [query, setQuery] = useState("");
// πŸ’₯ this is our ever so important state variable.On initial load is set to !
  const [currentPage, setCurrentPage] = useState(1);


// πŸ’₯ V2 πŸ‘‡ V2 version2 of implementing change of 
//current items using useEffect instead of mine original one
// const [currentItems, setCurrentItems] = useState([]);


//πŸ’₯ this predefined contentPerPage we would like to display
  const [itemsPerPage] = useState(8);
  const [{ items, isLoading, isError, search }, setSearch] =
    useDataFetch(query);

  const handleChange = (q) => {
    setQuery(q);
  };
  const handleSearch = () => {
    setSearch(query);
    setCurrentPage(1);
    setQuery("");
  };

//πŸ’₯ V2 πŸ‘‡
  // useEffect(() => {
  //   const indexOfLastItem = currentPage * itemsPerPage; // 8
  //   const indexOfFirstItem = indexOfLastItem - itemsPerPage; // 0
  //   setCurrentItems(items.slice(indexOfFirstItem, indexOfLastItem)); // items.slice(8,16)
  // }, [currentPage, items, itemsPerPage]);

  // Get current posts

//πŸ’₯ This our slicing implementation in practice
// V2 πŸ‘† you can use useEffect hook instead of this implementation
  const indexOfLastItem = currentPage * itemsPerPage; // 8
  const indexOfFirstItem = indexOfLastItem - itemsPerPage; // 0
  const currentItems = items.slice(indexOfFirstItem, indexOfLastItem); // items.slice(0,8)

  // Change page

// πŸ’₯ this the state setter which will change current page variable and cause re render. it is passed as a prop to Pagination component so whenever button is click will trigger this state setter and cause re-render
  const paginate = (pageNumber) => setCurrentPage(pageNumber);

  return (
    <div className="container">
      <Header />
      <Search
        handleChange={handleChange}
        handleSearch={handleSearch}
        inputText={query}
      />
      {isError && <div> Something went wrong ...</div>}
      {isLoading ? (
        <Spinner />
      ) : (
        <>
          <CharacterGrid items={currentItems} />
          <Pagination
            itemsPerPage={itemsPerPage}
            totalItems={items.length}
            paginate={paginate}
            currentPage={currentPage}
          />
        </>
      )}
    </div>
  );
};

export default App;

Enter fullscreen mode Exit fullscreen mode

Character Component( a child Component of our Character Grid) implementation is simple, it just receives currentItems as a prop and renders a simple list item.

The Pagination Component looks like thisπŸ‘‡

//Pagination.js
import React from "react";

const Pagination = ({ itemsPerPage, totalItems, paginate, currentPage }) => {
//πŸ’₯ simple loop which generates all the potential page number button. Use can use different method such as Array fill() .Important bit here is using Math.ceil. Assume we have 9 item in totalItems and we want to display 4 itemsPerPage => it will generate 3 pages 2 pages with 4 itemsPerPage and 1 with only one.
  const pageNumbers = [];
  for (let i = 1; i <= Math.ceil(totalItems / itemsPerPage); i++) {
    pageNumbers.push(i);
  }
  return (
    <nav>
      <ul className="pagination">
 {/* πŸ’₯ little conditional clause for only displaying the buttons if total number of  pages is bigger than 1 */}
        {pageNumbers.length > 1
          ? pageNumbers.map((number) => (
              <li
                key={number}
 {/* πŸ’₯ little UX touch to indicate currentPage by giving it active class */}
                className={`page-item ${
                  currentPage === number ? "active" : null
                }`}
              >
                <a
 {/* πŸ’₯ as mentioned previously important state setter handler. Onclick will change currentPage state variable by using paginate function we passed to this component.  */}
                  onClick={() => paginate(number)}
                  href="!#"
                  className="page-link"
                >
                  {number}
                  <sup>{number}</sup>
                </a>
              </li>
            ))
          : null}
      </ul>
    </nav>
  );
};

export default Pagination;


Enter fullscreen mode Exit fullscreen mode

And that's it ! I hope this was helpful. I am still pretty new to coding let alone technical writing, so any feedback about the code would be much appreciated.

You can find the full code in my GitHub repo here.

Our pagination implementation was added to the React App based on a superb Brad Traversy's You Tube tutorial video, which is a beginner intro guide into data fetching in React.js. Please check out his channel, but I am sure Brad is well known and does not need any introduction β€” his channel is an amazing resource for any Coding NewBie.

πŸ’– πŸ’ͺ πŸ™… 🚩
mikegajdos
MikeGajdos

Posted on November 15, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related