Support Online
Skip to main content

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:

  1. Project setup and installation of libraries
  2. Create a country card component
  3. Paging logic (page numbers, limit, neighbors)
  4. Merge into main app
  5. 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=&#123;kod&#125; format="png" pngSize=&#123;64&#125; 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">&#123;region&#125;</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(&#123; aktifSayfa &#125;, () => 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 &#123; toplamSayfa &#125; = this;
const &#123; aktifSayfa &#125; = 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 &#123; aktifSayfa &#125; = 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=&#123;i&#125;><a href="#" onClick={this.handleSolaKaydir}>&laquo;</a></li>;
if (sayfa === RIGHT_PAGE)
return <li key=&#123;i&#125;><a href="#" onClick={this.handleSagaKaydir}>&raquo;</a></li>;
return (
<li key=&#123;i&#125; className={`page-item${aktifSayfa === sayfa ? ' active' : ''}`}>
<a href="#" className="page-link" onClick={this.handleClick(sayfa)}>&#123;sayfa&#125;</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, &#123; Component &#125; 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(&#123; tumUlkeler &#125;);
}

onSayfaDegisti = veri => {
const &#123; tumUlkeler &#125; = 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>&#123;toplamUlke&#125;</strong> Ülke</h2>
{aktifSayfa && <span>Sayfa &#123;aktifSayfa&#125;/&#123;toplamSayfa&#125;</span>}
</div>
<Sayfalama toplamKayit=&#123;toplamUlke&#125; sayfaLimiti=&#123;18&#125; sayfaKomsulari=&#123;1&#125; onSayfaDegisti={this.onSayfaDegisti} />
{guncelUlkeler.map(u => <UlkeKarti key={u.cca3} ulke=&#123;u&#125; />)}
</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

  1. Can I use infinite scroll instead of pagination?

Yes. However, pagination offers the user more control over the dataset.

  1. Why is total registration mandatory?

Required to calculate the total number of pages.

  1. 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.

  1. 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.