Build this pagination in your React
Tags: react, javascript, webdev, nextjs
Published: 2021-11-07 02:40:11

---
canonical_url: "https://www.thangphan.xyz/posts/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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p3vzxhmhgkuwkfx4w6r8.png)

### 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!

```tsx
npx create-next-app@latest --typescript
```

**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.

```tsx
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
```

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:

```tsx
const [pageIndex, setPageIndex] = React.useState(0)
```

By default, the current page is 0.

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

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

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

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

Now, we are able to change the current page.

![Page Changing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jxnf5gai61bxruupblxw.gif)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4lzyk4pq3jjxtcfbjc1.png)

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m36ollmy1m8l8fyg1f4a.png)

#### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uuv954q8bep9e6ah22g9.png)

### 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.

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

#### 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.

```tsx
{
  Array.from({length: 5}, (_, idx) => (
    <li
      className={`${styles.pageItem} ${idx === 0 ? styles.currentPage : ''}`}
      onClick={() => handlePageChange(pageIndex + idx)}
    >
      {pageIndex + idx + 1}
    </li>
  ))
}
```

#### 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.

```tsx
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>
  )
}
```

![Next Page Changing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3efhxyc4f6idrednwzut.gif)

### 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.

```tsx
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
```

And we have the new pagination!

![Five Page Changing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wtctv9z3nnkjrxdtvp2v.gif)

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

![Seven Page Changing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y5o0zmwuqsp9s51y716d.gif)

### 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?