Herkese merhabalar. Bu yazımda sizlere “react-paginate” gibi bir npm paketi kullanmadan react projelerinizde sadece Javascript ve React Hooklarını kullanarak nasıl pagination (sayfalandırma) yapabileceğinizi anlatıyor olacağım. Hadi başlayalım.
Google da react ile pagination yapımı diye arattığınızda karşınıza bir çok kaynak çıkmakta ama kaynakların içerisine girdiğinizde karşınıza genelde bir npm paketi kullanarak pagination yapıldığı çıkmakta. Ama ben bu yazımda sizlere ek bir npm paketi kullanmadan nasıl pagination yapabileceğinizi anlatıyor olacağım. Ek olarak tüm kodları bu blog yazısının en altında sizler ile paylaşıyor olacağım.
Npm Nedir?
Pagination başlamadan önce npm nedir ona bakalım. Npm, Javascript programlama dili için geliştirilmiş olan ve Node.js’in standart olarak kabul ettiği bir paket yönetim sistemidir. Bu paket sisteminde her türlü yazılım ihtiyacını karşılayan paket bulunabilmektedir. Aynı şekilde React projelerinizde de pagination ihtiyacınızı karşılayan react-paginate,react-js-pagination ve react-responsive-pagination gibi paketler bulunmaktadır. Biz bu paketleri kullanmadan pagination yapıyor olacağız.
Yapının Ön Hazırlığı
Aşağıdaki komut ile ilk önce bir react projesi oluşturalım.
npx create-react-app react-pagination
Ardından sadece App.js ve App.css dosyalarında küçük bir temizleme yapacağız. App.js dosyası aşağıdaki gibi olacaktır. App.css dosyasının içerisindeki bütün css kodlarını silebilirsiniz.
import './App.css'
function App() {
return (
<div className='App'>
</div>
)
}
export default App
Şimdi pagination kodlaması yapmadan projemizi ve yapıyı oluşturmuş olduk. Bundan sonra artık pagination yapımına başlıyor olacağız.
Npm Paketsiz Pagination Yapımı
Başlamadan önce Api kaynağı olarak “https://jsonplaceholder.typicode.com/posts” kaynağını kullanıyor olacağız. Buradan bize 100 tane post datası geliyor olacak. İstediğimiz sayıya bağlı olarak postları gösteriyor olacağız. Aynı zamanda ona bağlı olarak da pagination sayfa sayısı oluşacaktır. Şimdi stateleri oluşturalım.
Statelerin Oluşturulması
Burada iki tane state oluşturacağız. Bir tanesi bizim Api den gelen datayı array olarak tutacak. Diğeri ise pagination değerlerini tutuyor olacak. Pagination state i bir obje olarak currentPage ve dataShowLenght değerlerini tutuyor olacak.
const [data, setData] = useState([]);
const [pagination, setPagination] = useState({
currentPage: 1,
dataShowLenght: 3,
});
- currentPage : Geçerli olan pagination sayfasını belirtiyor olacak.
- dataShowLenght : Sayfa içerisinde gösterilecek olan post sayısını belirtiyor olacak.
Api’den Data Alımı
Aşağıdaki kodlama ile “https://jsonplaceholder.typicode.com/posts” adresinden posts datasını alıp data state ine aktarmış olduk.
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((res) => res.json())
.then((data) => setData(data));
}, []);
Toplam Sayfa Sayısının Belirlenmesi
Oluşacak olan pagination sayfa sayısını belirlemek için basit bir bir formül vardır. Data sayısını gösterilecek olan data sayısına böldüğümüzde oluşacak olan sayfa sayısını bulmuş oluruz. Eğer oluşan değer ondalık sayı bir sayı değeri olur ise bu sayıyı bir üst sayıya yuvarlamamız gerekli. Buradaki mantık tüm dataları gösterecek olan doğru totalPage sayısını elde etmektir. Kalan değerleri de son sayfaya eklemiş oluyoruz.
Mesela 10 içerik olduğunda üçer tane olarak göstermek istediğimizde elde 1 tane kalan olur onu da bir sonraki sayfada tek başına gösteririz. Bu sayede 10 içerik için 4 pagination sayfası oluşmuş olur. Bunun içinde Math.ceil() metodunu kullanıyor olacağız. Bu metod ondaklıklı sayıyı bir üst sayıya yuvarlar. Sonuç 3.11 ise 4’e yuvarlayacaktır. İlk 3 sayfada 3 tane son sayfada ise 1 tane data gösterilmiş olacak.
const totalPage = Math.ceil(data.length / pagination.dataShowLenght);
Yukarıda data sayısını gösterilecek olan dataShowLenght sayısına bölüp, Math.ceil() metodu ile ondalıklı ise bir üst sayıya yuvarlayıp, totalPage değişkenine atamış olduk.
Pagination Butonlarına Tıklayınca Çalışacak Fonksiyonlar
Burada oluşturulacak olan pagination buttonlarının işlevselliği için hazırlanan fonksiyonlara bakıyor olacağız. Sayılara, Next ve Prev olmak üzere 3 tane fonksiyon oluşturacağız.
Sayılara Basınca CurrentPage Değişimi Sağlayan Fonksiyon
Aşağıdaki fonksiyon da currenPage değeri gelen page değeri ile değiştiriliyor. dataShowLenght değeri aynı kalıyor.
const paginationPage = (page) => {
setPagination({ ...pagination, currentPage: page });
};
Next Butonuna Basınca CurrentPage Değişimi Sağlayan Fonksiyon
Aşağıdaki fonksiyon da currenPage değeri şuan ki currentPage değerinin bir artırmayı sağlıyor. İf sorgusunda ise pagination.currentPage değeri totalPage değerinden küçük ise her seferinde currentPage değerini 1 artıracak. Ama eşit veya büyük olursa currentPage değerine totalPage değerini atayacak. Burada yapılmak istenilen son pagination sayfasına gelindiğinde next butonunun işlevsiz kalmasını sağlamak. dataShowLenght değeri ise aynı kalacaktır.
const paginationNext = () => {
if (pagination.currentPage < totalPage) {
setPagination({ ...pagination, currentPage: pagination.currentPage + 1 });
} else {
setPagination({ ...pagination, currentPage: totalPage });
}
};
Prev Butonuna Basınca CurrentPage Değişimi Sağlayan Fonksiyon
Aşağıdaki fonksiyon da currenPage değeri şuan ki currentPage değerinin bir azaltmayı sağlıyor. İf else sorgusunda ise pagination.currentPage değeri 1 değerinden büyük ise her seferinde currentPage değerini 1 azaltacaktır. Ama eşit veya küçük olursa currentPage değerine 1 değerini atayacak. Burada yapılmak istenilen ilk pagination sayfasına gelindiğinde prev butonunun işlevsiz kalmasını sağlamak. dataShowLenght değeri ise aynı kalacaktır.
const paginationPrev = () => {
if (pagination.currentPage > 1) {
setPagination({ ...pagination, currentPage: pagination.currentPage - 1 });
} else {
setPagination({ ...pagination, currentPage: 1 });
}
};
Yukarıdaki 3 fonksiyonda pagination state değerini değiştirdiği için componentin yeniden render olmasına sebep olacaktır. Şimdi ise return içerisindeki kodlara bakalım ve anlamaya çalışalım.
Return Alanı Kodları
Aşağıdaki kodlar component içerisindeki return alanındaki tüm kodları içermektedir.
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>
</>
);
Return İçerisinde Postların Listelenmesi
Şimdi cardArea classına sahip div içerisindeki kodları inceleyelim. Burada Api den gelen postlarımızı listeliyor olacağız.
<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>
Yukarıdaki kodlama sizlere biraz karmaşık gelmiş olabilir. Basit bir anlatım ile anlatmaya çalışacağım. Map metodu ile array içerisindeki veriyi belirli bir işlemden geçirip ekrana yazdırdığımızı map blog yazımda öğrenmiştik. Buraya kadar alaşılmayan bir şey yoktur sanırım.😄 Ama slice tam olarak burada ne yapıyor. Map metodu data içerisindeki tüm datayı işlemden geçirip return edecektir. Ama bizim sadece belirli bir aralıktaki datayı ekrana yazdırmamız gerekiyor. İşte slice de bizim tam olarak bu işimize yaramakta. pagination.currentPage bizim geçerli olan pagination sayfasını belirtiyordu. pagination.dataShowLenght ise gösterilecek post sayısını belirtiyordu. Şimdi yukarıdaki slice içindeki formülü inceleyelim.
(pagination.currentPage - 1) * pagination.dataShowLenght,
pagination.dataShowLenght * pagination.currentPage
Yukarıdaki formülde pagination.currentPage yeride 1 değerini, pagination.dataShowLenght değerini state i ilk oluşturduğumuzda yukarıda 3 olarak belirlemiştik. Şimdi bu değerlere göre yukarıdaki formülün çıktısı “0,3” olacaktır. Slice burada data arrayi içerisindeki 0 index’i dahil 3 index’i hariç postları map ile return edep ekrana yazdıracaktır. pagination.currentPage değiştikçe data içerisindeki gösterilecek datanın aralığı da değişecektir. Böylece pagination da hangi sayıya basarsanız ona göre yukarıdaki formül çalışıp sizlere postları gösterecektir. pagination.dataShowLenght değerini ben state’i oluştururken 3 olarak belirledim. Sizler istediğinizi gibi değiştirebilirsiniz. Umarım buradaki mantık anlaşılmıştır.
Return İçerisindeki Pagination Yapısı
Evet için en karmaşık kısmına geldik. Burada ilim var diyebilirim. 😄 Baya karmaşık bir yapı var. Sizlere an anlaşılır şekilde anlatmaya çalışacağım.
<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>
paginationPrev() ve paginationNext() fonksiyonların tam olarak ne yaptıklarını yukarıda anlatmıştım. Burada anlatmadığım kısım olan paginationArea() fonksiyonuna bakıyor olacağız.
Pagination Sayılarının Butonlarının Oluşturulması
paginationArea() foksiyonu bizlere pagination sayılarının yapısını geriye döndürmekte. Şimdi bu fonksiyon içerisindeki kodlara bakalım.
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;
};
Yukarıda kodlarda items array i pagination içindeki sayı elemanlarını tutuyor olacak. threePoints ise pagination içerisinde gösterilecek sayıları kısıtlayıp üç nokta olarak göstermek amaçlı kullanılacak. Şimdi for döngüsü bize totalPage sayısına bağlı olarak çalışarak bize itemları üretecek. totalPage değeri Apiden gelen 100 dataya bağlı olarak 34 olacaktır. Buradaki formülü yukarıda açıklamıştım. Şimdi bu kadar itemı ekranda göstermemiz büyük sıkıntı oluşturacaktır. Ekranda taşma yapacaktır. Burada bir kısıtlama yapmamız lazım.
number <= 1 ||
number >= totalPage ||
(number >= pagination.currentPage - 1 &&
number <= pagination.currentPage + 1)
Yukarıdaki if sorgu değerine uyan itemlar gösterilirken uymayan itemler ise 3 nokta olarak gösterilecek. threePoints kullanma amacımız ise yukarıdaki if sorgusuna uymayan her item için her seferinde 3 nokta koyulmaması için for döngüsünde if else sorgusunda bir kez else ye düştüğünde işlem , işlem sonunda threePoints değerini false a eşitliyorum. Bu sayede if else sorgusunda işlem tekrardan else ye düştüğünde threePoints === true if sorgusunu karşılamadığı için bir kez daha ekrana 3 nokta eklemiyor.
Pagination Çıktısı
Şimdi ise yukarıda yazdığımız kodların çıktısına bakalım.
Oluşturulan Pagination Yapısının Tüm Kodları
Burada sizler ile pagination yapısında kodlamış olduğum tüm kodları sizler ile paylaşıyor olacağım.
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;
}
}
Son
Evet, bu blog yazımda React ile pagination yapısını bir npm paketi kullanmadan oluşturmuş olduk. Umarım faydalı bir blog yazısı olmuştur. Bir sonraki blog yazımda görüşmek üzere…