DEV Community 👩‍💻👨‍💻

Cover image for Build this pagination in your React
Phan Công Thắng
Phan Công Thắng

Posted on • Originally published at thangphan.xyz

Build this pagination in your React

I have data that is fetched from the API, and I would like to paginate this data. In this post, I’m going to implement pagination using React.

Requirement

Suppose that the API returns for me the total page value, and I need to use this value to make pagination. And I also need to have the current page in case the user moves on other pages.

This is the requirement:

Simple Requirement

Flow

Now, we had the simple requirement. I'm going to draw the flow before moving the coding step.

In this example, I think I need to have:

  1. Render all pages based on the total page value.
  2. A state pageIndex which points to the current page.

Coding

First of all, we need to have a Next.js project. Let's do it quickly!

npx create-next-app@latest --typescript
Enter fullscreen mode Exit fullscreen mode

Step1: Render all pages using the total page value.

Just for the demo, so I'm going to hard code the total page value. I set it to 20.

import type {NextPage} from 'next'
import styles from '../styles/Home.module.css'

const PAGE_TOTAL = 20

const Home: NextPage = () => {
  return (
    <div className={styles.container}>
      <ul className={styles.pagination}>
        {Array.from({length: PAGE_TOTAL}, (_, idx) => (
          <li className={styles.pageItem}>{idx + 1}</li>
        ))}
      </ul>
    </div>
  )
}

export default Home
Enter fullscreen mode Exit fullscreen mode

Notice how I render the pages, and the number of the page just use Array.from and idx + 1.

Step2: Make the current page.

In this step, I need to make the current page.

First, define a new state in React:

const [pageIndex, setPageIndex] = React.useState(0)
Enter fullscreen mode Exit fullscreen mode

By default, the current page is 0.

Next, add a function that helps us to change the current page.

function handlePageChange(idx: number) {
  setPageIndex(idx)
}
Enter fullscreen mode Exit fullscreen mode

Finally, add the style for the current page, and the onClick event.

<li
  className={`${styles.pageItem} ${
    idx === pageIndex ? styles.currentPage : ''
  }`}
  onClick={() => handlePageChange(idx)}
>
  {idx + 1}
</li>
Enter fullscreen mode Exit fullscreen mode

Now, we are able to change the current page.

Page Changing

So far, we almost finished the pagination. But suppose that I only want to render 5 pages on the screen, and whenever I click on another page, the pages automatically increase.

Can I make it?

Yes, Let's go forward!

Next requirement

In this section, we are going to make our pagination more interactive. We need to render 5 pages on the screen, and the other pages automatically change based on the current page.

For ease of imagination, take a look at the image below:

New Requirement

Next Flow

Based on the requirement above, to implement the new pagination, I need to make a new flow for my pagination.

We have three cases:

  1. The total page value is less than 5.
  2. The total page value is greater than 5 plus the pageIndex value.
  3. The total page value is less than 5 plus the pageIndex value.

Let's break down these cases above in more detail!

The total page value is less than 5.

I set the total page value is 20, it definitely doesn't happen. But in the real world, maybe it will happen.

In this case, we don't need to change the logic code anymore. Just keep the code in the previous requirement.

The total page value is greater than 5 plus the pageIndex value.

Whenever we click the new page, the clicked page will move on the first positions and the remaining pages automatically are rendered.

The Second Case

The total page value is less than 5 plus the pageIndex value.

In this case, we can't automatically increase the pages, just change the current page value.

The Third Case

Next Coding

We need to change three things in the previous code:

  • The number of the page.
  • The total pages is rendered on the screen.
  • The function which we use in order to change the current page.
  • The current page logic.

The total page value is less than 5.

<li
  className={`${styles.pageItem} ${
    idx === pageIndex ? styles.currentPage : ''
  }`}
  onClick={() => handlePageChange(idx)}
>
  {idx + 1}
</li>
Enter fullscreen mode Exit fullscreen mode

The total page value is greater than 5 plus the pageIndex value.

In this case, we need to implement these requirements below:

  • The number of the page is the current page index plus current index where we clicked.
  • The total pages: 5.
  • The current page index always zero.
{
  Array.from({length: 5}, (_, idx) => (
    <li
      className={`${styles.pageItem} ${idx === 0 ? styles.currentPage : ''}`}
      onClick={() => handlePageChange(pageIndex + idx)}
    >
      {pageIndex + idx + 1}
    </li>
  ))
}
Enter fullscreen mode Exit fullscreen mode

The total page value is less than 5 plus the pageIndex value.

In this case, we need to implement these requirements below:

  • The right pages that are calculated from the current page index to the total page value, it's not equal to 5, so we need to take the remaining pages on the left side and the start index is the first page in the left side pages.
  • The total pages: 5.
  • The current page index always zero.
const PAGE_TOTAL = 20

const Home: NextPage = () => {
  const [pageIndex, setPageIndex] = React.useState(17)

  function handlePageChange(idx: number) {
    setPageIndex(idx)
  }

  // the right pages: 18,19,20
  // the left pages: 16,17
  // the start index: 15(page 16)
  const numberOfRightPages = PAGE_TOTAL - pageIndex
  const numberOfLeftPages = 5 - numberOfRightPages
  const startPageIndex = pageIndex - numberOfLeftPages

  return (
    <div className={styles.container}>
      <ul className={styles.pagination}>
        {Array.from({length: 5}, (_, idx) => (
          <li
            key={`pagination-items-${idx}`}
            className={`${styles.pageItem} ${
              startPageIndex + idx === pageIndex ? styles.currentPage : ''
            }`}
            onClick={() => handlePageChange(startPageIndex + idx)}
          >
            {startPageIndex + idx + 1}
          </li>
        ))}
      </ul>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Next Page Changing

Improvement

We have three cases, consider these cases, there are four different logic.

  1. The number of page.
  2. The current page check.
  3. The page index.
  4. The total page.

And we can improve our code by writing the component using
Inversion of Control method.

import type {NextPage} from 'next'
import styles from '../styles/Home.module.css'
import * as React from 'react'

const PAGE_TOTAL = 20
const NUMBER_PAGE = 5

function Pagination({
  length,
  isCurrentPage,
  goToNewPage,
  makeNumberPage,
}: {
  length: number
  isCurrentPage: (idx: number) => boolean
  goToNewPage: (idx: number) => void
  makeNumberPage: (idx: number) => number
}) {
  return (
    <ul className={styles.pagination}>
      {Array.from({length}, (_, idx) => (
        <li
          className={`${styles.pageItem} ${
            isCurrentPage(idx) ? styles.currentPage : ''
          }`}
          onClick={() => goToNewPage(idx)}
        >
          {makeNumberPage(idx)}
        </li>
      ))}
    </ul>
  )
}

const Home: NextPage = () => {
  const [pageIndex, setPageIndex] = React.useState(0)

  function handlePageChange(idx: number) {
    setPageIndex(idx)
  }

  if (PAGE_TOTAL < NUMBER_PAGE) {
    return (
      <Pagination
        length={PAGE_TOTAL}
        isCurrentPage={(idx) => idx === pageIndex}
        goToNewPage={(idx) => handlePageChange(idx)}
        makeNumberPage={(idx) => idx + 1}
      />
    )
  }

  if (PAGE_TOTAL >= pageIndex + NUMBER_PAGE) {
    return (
      <Pagination
        length={NUMBER_PAGE}
        isCurrentPage={(idx) => idx === 0}
        goToNewPage={(idx) => handlePageChange(pageIndex + idx)}
        makeNumberPage={(idx) => pageIndex + idx + 1}
      />
    )
  }

  if (PAGE_TOTAL < pageIndex + NUMBER_PAGE) {
    // the right pages: 18,19,20
    // the left pages: 16,17
    // the start index: 15(page 16)
    const numberOfRightPages = PAGE_TOTAL - pageIndex
    const numberOfLeftPages = NUMBER_PAGE - numberOfRightPages
    const startPageIndex = pageIndex - numberOfLeftPages

    return (
      <Pagination
        length={NUMBER_PAGE}
        isCurrentPage={(idx) => startPageIndex + idx === pageIndex}
        goToNewPage={(idx) => handlePageChange(startPageIndex + idx)}
        makeNumberPage={(idx) => startPageIndex + idx + 1}
      />
    )
  }

  throw new Error(`Just avoid the error comes from typescript!`)
}

export default Home
Enter fullscreen mode Exit fullscreen mode

And we have the new pagination!

Five Page Changing

You can change the NUMBER_PAGE value, e.g: I change it to 7.

Seven Page Changing

Conclusion

We just created pagination in a React app. In the first example, everything seems easy, but if we add some features to our pagination we need more code than we think. So why don't think some ideal for your pagination and play with it?

Top comments (2)

Collapse
 
lukeshiru profile image
Luke Shiru

Ideally you should use a for the page numbers instead of just li, and you could wrap the ul with a nav:

<nav role="navigation" aria-label="Pagination Navigation">
    <ul>
        <li><a href="/page-1">1</a></li>
        <li><a href="/page-2">2</a></li>
        <li><a href="/page-3">3</a></li>
        <li><a href="/page-4">4</a></li>
        <li><a href="/page-5">5</a></li>
    </ul>
</nav>
Enter fullscreen mode Exit fullscreen mode

Remember to always test your components with the keyboard, not only with the mouse. Besides that, from React this could use some improvement as well, but it works as it is and is not high-priority like the missing a11y.

Cheers!

Collapse
 
thangphan37 profile image
Phan Công Thắng Author

Awesome! Missing a11y is big problem! Thank you @lukeshiru !

Search No More

Join DEV and MongoDB and build an application with full-text search capabilities using MongoDB Atlas and Atlas Search for the DEV x MongoDB Atlas Hackathon 2022.

Join the Hackathon