DEV Community

Cover image for How to Create a Table with Pagination in React
Francisco Mendes
Francisco Mendes

Posted on

How to Create a Table with Pagination in React

Overview

One of the behaviors that practically all of us have is going to the npm repository to choose a library that helps us in creating the pagination of our tables.

Like many other components of React, we can install a dependency that helps this process, but we always end up being limited in many ways and one of them is styling.

For this reason I decided to write this article, despite being a simple solution it is something that is easily extensible and customizable.

Today's example

The idea of today's application is to create a table that will contain a total of six rows, each of these rows will be a country. We don't want to make all countries visible though, so we'll create a pagination and divide the countries by pages according to the number of elements we want per page.

At the end of the article I'll give you the link to the github repository so you can test the example.

Let's code

Today we are not going to install any dependencies, so we can go directly to the code. First we will start by creating a JavaScript file that will contain the data we want to show in the table.

// @/src/data/countries.js
export default [
  {
    id: 1,
    name: "Poland",
    language: "Polish",
    capital: "Warsaw",
  },
  {
    id: 2,
    name: "Bulgaria",
    language: "Bulgarian",
    capital: "Sofia",
  },
  {
    id: 3,
    name: "Hungary",
    language: "Hungarian",
    capital: "Budapest",
  },
  {
    id: 4,
    name: "Moldova",
    language: "Moldovan",
    capital: "Chișinău",
  },
  {
    id: 5,
    name: "Austria",
    language: "German",
    capital: "Vienna",
  },
  {
    id: 6,
    name: "Lithuania",
    language: "Lithuanian",
    capital: "Vilnius",
  },
];
Enter fullscreen mode Exit fullscreen mode

As said before and as you can see now, we have six elements inside the array. However I don't want to render all six on the same page, I think for today's example the ideal number would be four elements per page.

So we can start by working on our hook which will be responsible for all the logic related to paging our table.

First of all, we have to know how many pages are going to be needed according to the number of elements we have in the array and the number of rows we want per page. To do this, let's create the following function:

// @/src/hooks/useTable.js

const calculateRange = (data, rowsPerPage) => {
  const range = [];
  const num = Math.ceil(data.length / rowsPerPage);
  let i = 1;
  for (let i = 1; i <= num; i++) {
    range.push(i);
  }
  return range;
};

// ...
Enter fullscreen mode Exit fullscreen mode

Basically we are creating an array that will contain the number of pages in our table. In this example we will have two pages because we are using the Math.ceil() method.

pages images

Now with the page range defined, now we have to get the elements corresponding to each page. That is, as we have six elements and we want four per page.

On the first page we will have elements one through four and on the second page we will have elements five and six. As shown in this image:

elements per page

To obtain this result, let's create the following function:

// @/src/hooks/useTable.js

const calculateRange = (data, rowsPerPage) => {
  const range = [];
  const num = Math.ceil(data.length / rowsPerPage);
  let i = 1;
  for (let i = 1; i <= num; i++) {
    range.push(i);
  }
  return range;
};

const sliceData = (data, page, rowsPerPage) => {
  return data.slice((page - 1) * rowsPerPage, page * rowsPerPage);
};

// ...
Enter fullscreen mode Exit fullscreen mode

As you can see in the function, we will have three arguments, the data, the page and the number of rows. And according to these data we will return an array with the corresponding elements (countries).

Now we can start working on our hook, for that we will need to import the hooks from react useState() and useEffect(). Our hook will have three arguments, the data, the current page and the number of rows per page.

// @/src/hooks/useTable.js
import { useState, useEffect } from "react";

// ...

const useTable = (data, page, rowsPerPage) => {
  // ...
};

export default useTable;
Enter fullscreen mode Exit fullscreen mode

Then we will have two states, one will be the range of our table that will be the pages and the second will be the slice of the elements of the current page.

// @/src/hooks/useTable.js
import { useState, useEffect } from "react";

// ...

const useTable = (data, page, rowsPerPage) => {
  const [tableRange, setTableRange] = useState([]);
  const [slice, setSlice] = useState([]);

  // ...
};

export default useTable;
Enter fullscreen mode Exit fullscreen mode

Then we'll use useEffect to be aware that some data is changed or if some function is invoked.

// @/src/hooks/useTable.js
import { useState, useEffect } from "react";

// ...

const useTable = (data, page, rowsPerPage) => {
  const [tableRange, setTableRange] = useState([]);
  const [slice, setSlice] = useState([]);

  useEffect(() => {
    // ...
  }, [data, setTableRange, page, setSlice]);

  // ...
};

export default useTable;
Enter fullscreen mode Exit fullscreen mode

Still in our useEffect, we are going to calculate the range of our table and we are going to store its data in our state, using the function calculateRange(). And we will do the same for slices using the sliceData() function. Then just return the range and slice in our hook.

// @/src/hooks/useTable.js
import { useState, useEffect } from "react";

// ...

const useTable = (data, page, rowsPerPage) => {
  const [tableRange, setTableRange] = useState([]);
  const [slice, setSlice] = useState([]);

  useEffect(() => {
    const range = calculateRange(data, rowsPerPage);
    setTableRange([...range]);

    const slice = sliceData(data, page, rowsPerPage);
    setSlice([...slice]);
  }, [data, setTableRange, page, setSlice]);

  return { slice, range: tableRange };
};

export default useTable;
Enter fullscreen mode Exit fullscreen mode

Now we can start working on the components of our table, so let's start with the footer that will contain the buttons that will be used to navigate between pages.

The footer of our table will receive four props, the range, the page, the slice and the setPage. Basically we want to dynamically add new buttons according to the data provided to us. If a page contains only one element and it is deleted, we will want to be redirected to the previous page.

// @/src/components/Table/TableFooter/index.jsx
import React, { useEffect } from "react";

import styles from "./TableFooter.module.css";

const TableFooter = ({ range, setPage, page, slice }) => {
  useEffect(() => {
    if (slice.length < 1 && page !== 1) {
      setPage(page - 1);
    }
  }, [slice, page, setPage]);
  return (
    // ...
  );
};

export default TableFooter;
Enter fullscreen mode Exit fullscreen mode

Then just make a map of the buttons according to the range.

// @/src/components/Table/TableFooter/index.jsx
import React, { useEffect } from "react";

import styles from "./TableFooter.module.css";

const TableFooter = ({ range, setPage, page, slice }) => {
  useEffect(() => {
    if (slice.length < 1 && page !== 1) {
      setPage(page - 1);
    }
  }, [slice, page, setPage]);
  return (
    <div className={styles.tableFooter}>
      {range.map((el, index) => (
        <button
          key={index}
          className={`${styles.button} ${
            page === el ? styles.activeButton : styles.inactiveButton
          }`}
          onClick={() => setPage(el)}
        >
          {el}
        </button>
      ))}
    </div>
  );
};

export default TableFooter;
Enter fullscreen mode Exit fullscreen mode

These are the footer styles:

/* @/src/components/Table/TableFooter/TableFooter.module.css */
.tableFooter {
  background-color: #f1f1f1;
  padding: 8px 0px;
  width: 100%;
  font-weight: 500;
  text-align: left;
  font-size: 16px;
  color: #2c3e50;
  border-bottom-left-radius: 15px;
  border-bottom-right-radius: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.button {
  border: none;
  padding: 7px 14px;
  border-radius: 10px;
  cursor: pointer;
  margin-right: 4px;
  margin-left: 4px;
}

.activeButton {
  color: white;
  background: #185adb;
}

.inactiveButton {
  color: #2c3e50;
  background: #f9f9f9;
}
Enter fullscreen mode Exit fullscreen mode

Now we can work on our table component. First we'll import the useState() hook, then we'll import the hook we created and also our footer.

Our table component will receive two arguments, the data and the number of rows per page.

// @/src/components/Table/index.jsx
import React, { useState } from "react";

import useTable from "../../hooks/useTable";
import styles from "./Table.module.css";
import TableFooter from "./TableFooter";

const Table = ({ data, rowsPerPage }) => {
  // ...
};

export default Table;
Enter fullscreen mode Exit fullscreen mode

Then we have to create a state to define the table page with an initial value of one. Then let's get the range and slice from our hook.

// @/src/components/Table/index.jsx
import React, { useState } from "react";

import useTable from "../../hooks/useTable";
import styles from "./Table.module.css";
import TableFooter from "./TableFooter";

const Table = ({ data, rowsPerPage }) => {
  const [page, setPage] = useState(1);
  const { slice, range } = useTable(data, page, rowsPerPage);
  return (
    // ...
  );
};

export default Table;
Enter fullscreen mode Exit fullscreen mode

Afterwards we will create the markdown of our table and we will make a map of the slices, finally we will pass the necessary props to our footer. Like this:

// @/src/components/Table/index.jsx
import React, { useState } from "react";

import useTable from "../../hooks/useTable";
import styles from "./Table.module.css";
import TableFooter from "./TableFooter";

const Table = ({ data, rowsPerPage }) => {
  const [page, setPage] = useState(1);
  const { slice, range } = useTable(data, page, rowsPerPage);
  return (
    <>
      <table className={styles.table}>
        <thead className={styles.tableRowHeader}>
          <tr>
            <th className={styles.tableHeader}>Country</th>
            <th className={styles.tableHeader}>Capital</th>
            <th className={styles.tableHeader}>Language</th>
          </tr>
        </thead>
        <tbody>
          {slice.map((el) => (
            <tr className={styles.tableRowItems} key={el.id}>
              <td className={styles.tableCell}>{el.name}</td>
              <td className={styles.tableCell}>{el.capital}</td>
              <td className={styles.tableCell}>{el.language}</td>
            </tr>
          ))}
        </tbody>
      </table>
      <TableFooter range={range} slice={slice} setPage={setPage} page={page} />
    </>
  );
};

export default Table;
Enter fullscreen mode Exit fullscreen mode

These are our table styles:

/* @/src/components/Table/Table.module.css */
.table {
  border-collapse: collapse;
  border: none;
  width: 100%;
}

.tableRowHeader {
  background-color: transparent;
  transition: all 0.25s ease;
  border-radius: 10px;
}

.tableHeader {
  background-color: #f1f1f1;
  padding: 12px;
  font-weight: 500;
  text-align: left;
  font-size: 14px;
  color: #2c3e50;
}

.tableHeader:first-child {
  border-top-left-radius: 15px;
}

.tableHeader:last-child {
  border-top-right-radius: 15px;
}

.tableRowItems {
  cursor: auto;
}

.tableRowItems:nth-child(odd) {
  background-color: #f9f9f9;
}

.tableCell {
  padding: 12px;
  font-size: 14px;
  color: grey;
}
Enter fullscreen mode Exit fullscreen mode

Last but not least, let's go to our App.jsx to import country data and our table. Then just pass the data as prop of our table and define the number of rows we want per page, which in this example I chose four.

// @/src/App.jsx
import React, { useState } from "react";

import countriesData from "./data/countries";
import styles from "./App.module.css";
import Table from "./components/Table";

const App = () => {
  const [countries] = useState([...countriesData]);
  return (
    <main className={styles.container}>
      <div className={styles.wrapper}>
        <Table data={countries} rowsPerPage={4} />
      </div>
    </main>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Styles of our App.jsx:

/* @/src/App.module.css */
.container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  width: 100vw;
}

.wrapper {
  width: 550px;
}
Enter fullscreen mode Exit fullscreen mode

You should get a result similar to this:

final app

As promised at the beginning of the article, to access the github repository click here.

Conclusion

As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 🧑🏻‍💻

Hope you have a great day! 👹

Discussion (0)