Hello everyone. In this article, I will be explaining how you can make pagination in your react projects using only Javascript and React Hooks without using an npm package like “react-paginate“. Let’s get started.
When you search for pagination with react in Google, you will see many resources, but when you enter the resources, you will see that pagination is usually done using an npm package. But in this article, I will be telling you how you can pagination without using an additional npm package.
What is npm?
Before starting pagination, let’s take a look at what npm is. Npm is a package management system developed for the Javascript programming language and accepted as a standard by Node.js. In this package system, there are packages that meet all kinds of software needs.
Likewise, there are packages such as react-paginate, react-js-pagination and react-responsive-pagination that meet your pagination needs in your React projects. We will be paginating without using these packages.
Preliminary Preparation of the Structure
Let’s first create a react project with the following command.
npx create-react-app react-pagination
Then we will do a little cleanup only in App.js and App.css files. App.js file will be as follows. You can delete all css codes in the App.css file.
import './App.css'
function App() {
return (
<div className='App'>
</div>
)
}
export default App
Now we have created our project and structure without pagination coding. After that, we will now start making pagination.
Pagination without Npm Package
Before we start, we will be using “https://jsonplaceholder.typicode.com/posts” as the API source. From here we will receive 100 post data. Depending on the number we want, we will show the posts. At the same time, depending on it, the number of pagination pages will be created. Now let’s create the statuses.
Creation of Statements
We will create two states here. One will keep the data coming from our API as an array. The other will hold the pagination values. Pagination state will hold currentPage and dataShowLenght values as an object.
const [data, setData] = useState([]);
const [pagination, setPagination] = useState({
currentPage: 1,
dataShowLenght: 3,
});
- currentPage : Will specify the current pagination page.
- dataShowLenght : It will specify the number of posts to be shown on the page.
Data Retrieval from Api
With the following coding, we received posts data from “https://jsonplaceholder.typicode.com/posts” and transferred it to the data state.
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((res) => res.json())
.then((data) => setData(data));
}, []);
Determining the Total Number of Pages
There is a simple formula to determine the number of pagination pages that will occur. When we divide the number of data by the number of data to be displayed, we find the number of pages that will occur. If the resulting value is a decimal number, we need to round this number to the next higher number.
The logic here is to get the correct totalPage number that will show all the data. We add the remaining values to the last page.
For example, when there are 10 content, when we want to show them as three, we will have 1 left and we will show it alone on the next page. In this way, 4 pagination pages are created for 10 content. We will be using the Math.ceil() method for this. This method rounds the decimal number to the next higher number. If the result is 3.11, it will round to 4. The first 3 pages will show 3 data and the last page will show 1 data.
const totalPage = Math.ceil(data.length / pagination.dataShowLenght);
Above, we divided the number of data by the number of dataShowLenght to be displayed, rounded it to the next higher number if it is decimal with the Math.ceil() method and assigned it to the totalPage variable.
Functions to Run When Clicking Pagination Buttons
Here we will be looking at the functions prepared for the functionality of the pagination buttons to be created. We will create 3 functions, Numbers, Next and Prev.
Function that changes CurrentPage when pressing numbers
In the function below, the currenPage value is replaced with the incoming page value. dataShowLenght value remains the same.
const paginationPage = (page) => {
setPagination({ ...pagination, currentPage: page });
};
Function Providing CurrentPage Change When Next Button is Pressed
In the function below, the currenPage value increases the currentPage value by one. In the if query, if the pagination.currentPage value is less than the totalPage value, it will increase the currentPage value by 1 each time. But if it is equal or greater, it will assign the totalPage value to the currentPage value.
What we want to do here is to make the next button non-functional when the last pagination page is reached. dataShowLenght value will remain the same.
const paginationNext = () => {
if (pagination.currentPage < totalPage) {
setPagination({ ...pagination, currentPage: pagination.currentPage + 1 });
} else {
setPagination({ ...pagination, currentPage: totalPage });
}
};
Function Providing CurrentPage Change When Prev Button is Pressed
In the function below, the currenPage value reduces the currentPage value by one. In the if else query, if the pagination.currentPage value is greater than 1, it will decrease the currentPage value by 1 each time.
But if it is equal or less, it will assign a value of 1 to the currentPage value. What we want to do here is to make the prev button non-functional when the first pagination page is reached. dataShowLenght value will remain the same.
const paginationPrev = () => {
if (pagination.currentPage > 1) {
setPagination({ ...pagination, currentPage: pagination.currentPage - 1 });
} else {
setPagination({ ...pagination, currentPage: 1 });
}
};
Since the above 3 functions change the pagination state value, it will cause the component to render again. Now let’s look at the code in return and try to understand it.
Return Field Codes
The following codes contain all the codes in the return field in the component.
return (
<>
<div className="cardArea">
{data
.slice(
(pagination.currentPage - 1) * pagination.dataShowLenght,
pagination.dataShowLenght * pagination.currentPage
)
.map((item, index) => {
return (
<div key={index} className="cardItem">
<div className="cardHeader">
<h2>{item.title}</h2>
</div>
<div className="cardBody">
<p>{item.body}</p>
</div>
</div>
);
})}
</div>
<div className="paginationArea">
<nav aria-label="navigation" className="">
<ul className="pagination">
<li className="page-item previous">
<a
className="page-link"
onClick={() => {
paginationPrev();
}}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-chevron-left">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
<span>Prev</span>
</a>
</li>
{paginationArea()}
<li className="page-item next">
<a
onClick={() => {
paginationNext();
}}
className="page-link">
<span>Next</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-chevron-right">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</a>
</li>
</ul>
</nav>
</div>
</>
);
Listing of Posts in Return
Now let’s examine the codes in the div with the cardArea class. Here we will be listing our posts from Api.
<div className="cardArea">
{data
.slice(
(pagination.currentPage - 1) * pagination.dataShowLenght,
pagination.dataShowLenght * pagination.currentPage
)
.map((item, index) => {
return (
<div key={index} className="cardItem">
<div className="cardHeader">
<h2>{item.title}</h2>
</div>
<div className="cardBody">
<p>{item.body}</p>
</div>
</div>
);
})}
</div>
The above coding may seem a bit complicated to you. I will try to explain it with a simple explanation. In my map blog post, we learned that with the map method, we pass the data in the array through a certain process and print it on the screen. I guess there is nothing unfamiliar so far ? But what exactly does slice do here. The map method will process all the data in the data and return it.
But we only need to print the data in a certain range on the screen. This is exactly what slice does for us. pagination.currentPage specifies our current pagination page. pagination.dataShowLenght specifies the number of posts to display. Now let’s examine the formula in the slice above.
(pagination.currentPage - 1) * pagination.dataShowLenght,
pagination.dataShowLenght * pagination.currentPage
In the above formula, we set pagination.currentPage to 1 and pagination.dataShowLenght to 3 when we first created the state. Now according to these values, the output of the above formula will be “0,3”. Here Slice will return the posts in the data array including index 0 and excluding index 3 with map and print them on the screen. As pagination.currentPage changes, the range of the data to be shown in the data will also change.
Thus, whichever number you press in the pagination, the above formula will work according to it and show you the posts. I set the pagination.dataShowLenght value as 3 when I created the state. You can change it as you wish. I hope the logic here is understood.
Pagination Structure in Return
Yes, we’ve come to the most complicated part. I can say there is lore here. ?There is a very complex structure. I will try to explain it to you in an understandable way.
<div className="paginationArea">
<nav className="">
<ul className="pagination">
<li className="page-item previous">
<a
className="page-link"
onClick={() => {
paginationPrev();
}}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-chevron-left">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
<span>Prev</span>
</a>
</li>
{paginationArea()}
<li className="page-item next">
<a
onClick={() => {
paginationNext();
}}
className="page-link">
<span>Next</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-chevron-right">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</a>
</li>
</ul>
</nav>
</div>
I have already explained exactly what the paginationPrev() and paginationNext() functions do above. Here we will be looking at the paginationArea() function, which is the part I did not explain.
Creating Buttons of Pagination Numbers
The paginationArea() function returns us the structure of the pagination numbers. Now let’s look at the code in this function.
const paginationArea = () => {
const items = [];
let threePoints = true;
for (let number = 1; number <= totalPage; number++) {
if (
number <= 1 ||
number >= totalPage ||
(number >= pagination.currentPage - 1 &&
number <= pagination.currentPage + 1)
) {
items.push(
<li
key={number}
className={`page-item ${
pagination.currentPage === number ? "active" : ""
}`}
onClick={() => {
paginationPage(number);
}}>
<a className="page-link">{number}</a>
</li>
);
} else {
if (threePoints === true) {
items.push(
<li key={number} className="page-item threePoints">
<a className="page-link">...</a>
</li>
);
threePoints = false;
}
}
}
return items;
};
In the codes above, the items array will be holding the number elements in the pagination. threePoints will be used to restrict the numbers to be shown in the pagination and show them as three points.
Now the for loop will work depending on the totalPage number to produce items for us. totalPage value will be 34 depending on 100 data coming from the API. I explained the formula here above. Now showing this many items on the screen will be a big problem. It will overflow on the screen. We need to make a restriction here.
number <= 1 ||
number >= totalPage ||
(number >= pagination.currentPage - 1 &&
number <= pagination.currentPage + 1)
While the items that match the if query value above will be shown, the items that do not match will be shown as 3 dots. Our purpose of using threePoints is to avoid putting 3 dots for each item that does not match the if query above, in the for loop, when the process falls to else once in the if else query, I set the threePoints value equal to false at the end of the process.
In this way, when the process falls to else again in the if else query, it does not add 3 dots to the screen once again because “threePoints === true” does not meet the if query,
Pagination Output
Now let’s look at the output of the codes we wrote above.
All Codes of the Created Pagination Structure
Here I will be sharing with you all the codes I have coded in the pagination structure.
import { useEffect, useState } from "react";
import "./App.css";
export default function App() {
const [data, setData] = useState([]);
const [pagination, setPagination] = useState({
currentPage: 1,
dataShowLenght: 3,
});
const totalPage = Math.ceil(data.length / pagination.dataShowLenght);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((res) => res.json())
.then((data) => setData(data));
}, []);
const paginationPage = (page) => {
setPagination({ ...pagination, currentPage: page });
};
const paginationArea = () => {
const items = [];
let threePoints = true;
for (let number = 1; number <= totalPage; number++) {
if (
number <= 1 ||
number >= totalPage ||
(number >= pagination.currentPage - 1 &&
number <= pagination.currentPage + 1)
) {
items.push(
<li
key={number}
className={`page-item ${
pagination.currentPage === number ? "active" : ""
}`}
onClick={() => {
paginationPage(number);
}}>
<a className="page-link">{number}</a>
</li>
);
} else {
if (threePoints === true) {
items.push(
<li key={number} className="page-item threePoints">
<a className="page-link">...</a>
</li>
);
threePoints = false;
}
}
}
return items;
};
const paginationNext = () => {
if (pagination.currentPage < totalPage) {
setPagination({ ...pagination, currentPage: pagination.currentPage + 1 });
} else {
setPagination({ ...pagination, currentPage: totalPage });
}
};
const paginationPrev = () => {
if (pagination.currentPage > 1) {
setPagination({ ...pagination, currentPage: pagination.currentPage - 1 });
} else {
setPagination({ ...pagination, currentPage: 1 });
}
};
return (
<>
<div className="cardArea">
{data
.slice(
(pagination.currentPage - 1) * pagination.dataShowLenght,
pagination.dataShowLenght * pagination.currentPage
)
.map((item, index) => {
return (
<div key={index} className="cardItem">
<div className="cardHeader">
<h2>{item.title}</h2>
</div>
<div className="cardBody">
<p>{item.body}</p>
</div>
</div>
);
})}
</div>
<div className="paginationArea">
<nav className="">
<ul className="pagination">
<li className="page-item previous">
<a
className="page-link"
onClick={() => {
paginationPrev();
}}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-chevron-left">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
<span>Prev</span>
</a>
</li>
{paginationArea()}
<li className="page-item next">
<a
onClick={() => {
paginationNext();
}}
className="page-link">
<span>Next</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-chevron-right">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</a>
</li>
</ul>
</nav>
</div>
</>
);
}
.cardArea {
max-width: 700px;
padding: 40px;
margin: auto;
width: 100%;
}
.cardItem {
border: 2px solid rgb(231, 231, 231);
padding: 16px;
min-height: 185px;
max-height: 185px;
border-radius: 10px;
box-shadow: 0 1px 6px 1px rgba(204, 204, 204, 0.3);
transition: all .2s ease;
background: rgb(252, 252, 252);
}
.cardItem:not(:last-child) {
margin-bottom: 20px;
}
.cardItem:hover {
box-shadow: 0 1px 12px 1px rgba(204, 204, 204, 0.5);
transform: scale(1.02)
}
.cardArea .cardHeader h2 {
font-size: 24px;
font-weight: 600;
margin: 0;
line-height: 26px;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
.cardBody p {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.paginationArea {
max-width: 700px;
margin: auto;
padding: 10px 0 60px 0;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
list-style-type: none;
padding: 0;
}
.page-item:not(.threePoints) {
margin: 0 6px;
border: 2px solid #ecf0f1;
font-size: 18px;
line-height: 18px;
font-weight: 600;
border-radius: 8px;
cursor: pointer;
transition: all.2s ease;
}
.page-item:not(.threePoints, .active):hover {
transform: scale(1.1);
box-shadow: 0 4px 16px #dde0e169;
background: #bdc3c7;
color: #ecf0f1;
}
.page-item.threePoints {
font-size: 28px;
line-height: 28px;
height: 40px;
width: 40px;
display: flex;
justify-content: center;
align-items: end;
}
.page-item:not(:last-child, :first-child, .threePoints) {
width: 40px;
min-width: 40px;
height: 40px;
max-height: 40px;
display: flex;
justify-content: center;
align-items: center;
}
.page-item:is(:last-child, :first-child) {
height: 40px;
max-height: 40px;
display: flex;
justify-content: center;
align-items: center;
background-color: #34495e;
color: #ecf0f1;
border: 3px solid #778797a8
}
.page-item:first-child {
padding: 0 16px 0 8px;
}
.page-item:last-child {
padding: 0 8px 0 16px;
}
.page-item.active {
background: #e74c3c;
color: #ecf0f1;
box-shadow: 0 4px 16px 0.2px #e74d3c55;
border: 3px solid #c0392b;
}
.page-link {
display: flex;
justify-content: center;
align-items: center;
}
.page-link svg {
margin-top: 3px;
}
/** Responsive */
@media (max-width: 576px) {
.cardArea {
padding: 20px;
}
.page-item:not(.threePoints) {
font-size: 15px;
line-height: 15px;
margin: 0 3px;
}
.page-item.threePoints {
font-size: 16px;
line-height: 16px;
height: 32px;
width: 32px;
}
.page-item:not(:last-child, :first-child, .threePoints) {
width: 32px;
min-width: 32px;
height: 32px;
max-height: 32px;
}
.page-link span {
display: none;
}
.page-link svg {
margin-top: 0px;
}
.page-item:is(:last-child, :first-child) {
height: 32px;
max-height: 32px;
min-width: 32px;
padding: 0;
}
}
The End
Yes, in this blog post, we have created the pagination structure with React without using an npm package. I hope it was a useful blog post. See you in my next blog post…