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",
},
];
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;
};
// ...
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.
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:
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);
};
// ...
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;
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;
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;
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;
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;
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;
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;
}
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;
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;
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;
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;
}
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;
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;
}
You should get a result similar to this:
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! 👹
Top comments (9)
Muito bom Chico!
Obrigado!
Well written and clear to understand
This is exceptional. Thank you.
Thanks for sharing this tutorial on creating a table with pagination in React! Pagination is an essential feature for any table displaying a large amount of data, and it's great to see how easy it is to implement with the react-bootstrap-table-next package.
For those looking to create a React table with additional features such as sorting and filtering, check out this React table blog for more information.
Thanks again for sharing this helpful tutorial!
Good afternoon , please I have an error in TableFooter.js
I need an help tu resolve it
Awesome
This was so helpful
This is very understandable, please have you done the same thing using typescript?
footer of the table fails if there are more number of pages for example more than 100