Creating a Custom Pagination Component with React
Showing large data sets on a single page is difficult in terms of both performance and user experience.
In this guide, we will create a dynamic and reusable pagination component using React.
In our application, we will show the countries of the world by breaking them into pieces and build the navigation logic from scratch.
🧠 Stage 1 – Plot Summary
Main technical topic: Developing custom, customizable pagination component with React.
The problem it solves: Showing large data sets (e.g. thousands of records) to the user piece by piece.
Steps:
- Project setup and installation of libraries
- Create a country card component
- Paging logic (page numbers, limit, neighbors)
- Merge into main app
- Custom styles with SCSS
🚀 Stage 2 – Preparing the Project
Start a new React app:
npx create-react-app genixnode-sayfalama
cd genixnode-sayfalama
Install the required dependencies:
npm install bootstrap@4.1.0 prop-types@15.6.1 react-flags@0.1.13 countries-api@2.0.1 node-sass@4.14.1
Include Bootstrap styles in the project:
// src/index.js
import "bootstrap/dist/css/bootstrap.min.css";
Add flag images:
mkdir public/img
cp -R node_modules/react-flags/vendor/flags public/img
These steps prepare both the data and style infrastructure.
🧩 Step 3 – Creating the Country Card Component
We write the CountryCard component that displays each country:
import React from 'react';
import PropTypes from 'prop-types';
import Flag from 'react-flags';
const UlkeKarti = ({ ulke }) => {
const { cca2: kod = '', region, name = {} } = ulke;
return (
<div className="col-sm-6 col-md-4 ulke-kart">
<div className="border rounded bg-light d-flex align-items-center p-0 my-3">
<div className="border-right px-2 bg-white">
<Flag country={kod} format="png" pngSize={64} basePath="./img/flags" />
</div>
<div className="px-3">
<span className="font-weight-bold text-dark d-block">{name.common}</span>
<span className="text-secondary text-uppercase">{region}</span>
</div>
</div>
</div>
);
};
UlkeKarti.propTypes = {
ulke: PropTypes.shape({
cca2: PropTypes.string.isRequired,
region: PropTypes.string.isRequired,
name: PropTypes.shape({
common: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
};
export default UlkeKarti;
This component displays the country's name, region, and flag.
🧮 Step 4 – Writing the Pagination Component
Pagination component that will create page numbers dynamically:
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
const LEFT_PAGE = 'LEFT';
const RIGHT_PAGE = 'RIGHT';
const range = (from, to, step = 1) => {
const out = [];
for (let i = from; i <= to; i += step) out.push(i);
return out;
};
class Sayfalama extends Component {
constructor(props) {
super(props);
const { toplamKayit = 0, sayfaLimiti = 18, sayfaKomsulari = 1 } = props;
this.sayfaLimiti = sayfaLimiti;
this.toplamKayit = toplamKayit;
this.sayfaKomsulari = Math.max(0, Math.min(sayfaKomsulari, 2));
this.toplamSayfa = Math.ceil(this.toplamKayit / this.sayfaLimiti);
this.state = { aktifSayfa: 1 };
}
componentDidMount() {
this.sayfayaGit(1);
}
sayfayaGit = (sayfa) => {
const { onSayfaDegisti = f => f } = this.props;
const aktifSayfa = Math.max(1, Math.min(sayfa, this.toplamSayfa));
const veri = {
aktifSayfa,
toplamSayfa: this.toplamSayfa,
sayfaLimiti: this.sayfaLimiti,
toplamKayit: this.toplamKayit
};
this.setState({ aktifSayfa }, () => onSayfaDegisti(veri));
};
handleClick = sayfa => e => { e.preventDefault(); this.sayfayaGit(sayfa); };
handleSolaKaydir = e => { e.preventDefault(); this.sayfayaGit(this.state.aktifSayfa - 1); };
handleSagaKaydir = e => { e.preventDefault(); this.sayfayaGit(this.state.aktifSayfa + 1); };
sayfaNumaralariniGetir = () => {
const { toplamSayfa } = this;
const { aktifSayfa } = this.state;
const toplamNumara = (this.sayfaKomsulari * 2) + 3;
if (toplamSayfa <= toplamNumara) return range(1, toplamSayfa);
const start = Math.max(2, aktifSayfa - this.sayfaKomsulari);
const end = Math.min(toplamSayfa - 1, aktifSayfa + this.sayfaKomsulari);
let sayfalar = range(start, end);
if (start > 2) sayfalar = [LEFT_PAGE, ...sayfalar];
if (end < toplamSayfa - 1) sayfalar = [...sayfalar, RIGHT_PAGE];
return [1, ...sayfalar, toplamSayfa];
};
render() {
if (!this.toplamKayit || this.toplamSayfa === 1) return null;
const { aktifSayfa } = this.state;
const sayfalar = this.sayfaNumaralariniGetir();
return (
<Fragment>
<nav aria-label="Sayfalama">
<ul className="pagination">
{sayfalar.map((sayfa, i) => {
if (sayfa === LEFT_PAGE)
return <li key={i}><a href="#" onClick={this.handleSolaKaydir}>«</a></li>;
if (sayfa === RIGHT_PAGE)
return <li key={i}><a href="#" onClick={this.handleSagaKaydir}>»</a></li>;
return (
<li key={i} className={`page-item${aktifSayfa === sayfa ? ' active' : ''}`}>
<a href="#" className="page-link" onClick={this.handleClick(sayfa)}>{sayfa}</a>
</li>
);
})}
</ul>
</nav>
</Fragment>
);
}
}
Sayfalama.propTypes = {
toplamKayit: PropTypes.number.isRequired,
sayfaLimiti: PropTypes.number,
sayfaKomsulari: PropTypes.number,
onSayfaDegisti: PropTypes.func
};
export default Sayfalama;
This class calculates page numbers based on the total number of records and handles page transition on clicks.
⚙️ Stage 5 – Main Application (App.js)
import React, { Component } from 'react';
import Countries from 'countries-api';
import './App.scss';
import Sayfalama from './components/Sayfalama';
import UlkeKarti from './components/UlkeKarti';
class App extends Component {
state = { tumUlkeler: [], guncelUlkeler: [], aktifSayfa: null, toplamSayfa: null };
componentDidMount() {
const { data: tumUlkeler = [] } = Countries.findAll();
this.setState({ tumUlkeler });
}
onSayfaDegisti = veri => {
const { tumUlkeler } = this.state;
const { aktifSayfa, toplamSayfa, sayfaLimiti } = veri;
const baslangic = (aktifSayfa - 1) * sayfaLimiti;
const guncelUlkeler = tumUlkeler.slice(baslangic, baslangic + sayfaLimiti);
this.setState({ aktifSayfa, guncelUlkeler, toplamSayfa });
};
render() {
const { tumUlkeler, guncelUlkeler, aktifSayfa, toplamSayfa } = this.state;
const toplamUlke = tumUlkeler.length;
if (toplamUlke === 0) return null;
return (
<div className="container mb-5">
<div className="row py-5">
<div className="d-flex justify-content-between align-items-center w-100 px-4 py-3">
<h2><strong>{toplamUlke}</strong> Ülke</h2>
{aktifSayfa && <span>Sayfa {aktifSayfa}/{toplamSayfa}</span>}
</div>
<Sayfalama toplamKayit={toplamUlke} sayfaLimiti={18} sayfaKomsulari={1} onSayfaDegisti={this.onSayfaDegisti} />
{guncelUlkeler.map(u => <UlkeKarti key={u.cca3} ulke={u} />)}
</div>
</div>
);
}
}
export default App;
🎨 SCSS – Simple and Modern Style
ul.pagination {
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
a.page-link:hover { background-color: #f7f7f7; }
.active .page-link {
background-color: #ddd;
color: #000;
}
}
🙋♀️ Frequently Asked Questions
- Can I use infinite scroll instead of pagination?
Yes. However, pagination offers the user more control over the dataset.
- Why is total registration mandatory?
Required to calculate the total number of pages.
- Will there be performance issues with real API data?
In large data sets, it is necessary to retrieve dynamic data from the API using LIMIT and OFFSET instead of slice.
- Can I write with hooks?
Yes, you can implement the same logic in a simpler way with useState and useEffect.
☁️ Result
You've now learned how to create a fully customizable pagination system with React. This structure is user-friendly, high-performance and modular.
💡 You can publish your project with high performance by hosting it on GenixNode.

