ساخت صفحه بندی سفارشی با React

در این مقاله، نحوه ساخت صفحه بندی سفارشی را با استفاده از React برای صفحه بندی مجموعه داده های بزرگ را به شما آموزش میدهیم. برا این که تا جای ممکن سادگی را حفظ کنیم، نمایش صفحه بندی شده ای از کشورها جهان را ایجاد می کنیم که در آن داده های همه کشورها را پیشاپیش داریم.

ساخت صفحه بندی سفارشی با React

اغلب اوقات، درگیر ساخت برنامه های وبی می شویم که لازم است مجموعه داده های بزرگی را از سرور  راه دور،‌API یا پایگاه داده ای که در جایی قرار گرفته است واکشی کنیم. برای مثال اگر یک سیستم پرداخت ایجاد می کنید،‌ واکشی هزاران تراکنش لازم است. اگر برنامه رسانه های اجتماعی باشد واکشی هزاران هزار نظر،‌مشخصات یا فعالیت های کاربران لازم است. هرکدام که باشد،‌ دو روش برای اداره داده، طوری که برای کاربر نهایی که با برنامه تعامل دارد طاقت فرسا نباشد،‌وجود دارد.

یکی از روش های متداولی که برای اداره پایگاه های داده بزرگ درنظر(و نه در عمل) استفاده از تکنیک صفحات بی پایان ( infinite scrolling technique ) است که وقتی کاربر تا انتهای صفحه به پیمایش ادامه می دهد  قطعات داده بیشتری بارگذاری می شود. این تکنیکی است که از آن در نمایش نتایج در تصاویر گوگل استفاده می کنند. همچنین در بسیاری از پلتفرم های شبکه های اجتماعی مانند فیسبوک در نمایش پست ها در نوارزمانی، توئیتر در نمایش توئیت های جدید است و غیره.

روش معروف دیگر برای اداره مجموعه داده های بزرگ استفاده از  pagination (صفحه بندی) است. وقتی اندازه مجموعه داده را می دانید pagination  موثر عمل می کند. ثانیا،‌ شما تنها قطعات داده مورد نیاز را براساس تعامل کاربران با صفحه بندی از مجموعه داده کل بارگذاری می کنید. در نمایش نتایج جستجوی گوگل از این تکنیک استفاده می شود.

این نسخه نمایشی آنچه در این مقاله می سازیم است.

نیازمندی ها

پیش از شروع،‌ باید Node را روی سیستم خود نصب کرده باشید. همچنین توصیه ما این است که بسته مدیریت Yarn را روی ماشین خود نصب کنید، چون به جای npm که با Node ارسال می شود از آن برای مدیریت بسته استفاده می کنیم. برای نصب yarn از لینک (https://yarnpkg.com/lang/en/docs/install/) استفاده کنید. برای برنامه React خود با استفاده از خط فرمان create-react-app  یک code boilerplate (به قسمتی از کد که باید در قسمت های مختلف برنامه اینکلود شود ، code boilerplate گوییم) می سازیم. همچنین مطمئن شوید که به صورت سراسری روی ماشین شما نصب شده است. اگر از npm >= 5.2 (NPM بالاتر از 5.2)استفاده می کنید دیگر نیازی به نصب create-react-app ندارید چون می توانیم از دستور npx استفاده کنیم. در آخر، فرض این مقاله این است که شما قبلا با React آشنا باشید.

شروع

ایجاد برنامه جدید

با استفاده از دستور زیر برنامه React جدید را شروع کنید. نام برنامه را به دلخواه انتخاب کنید.

create-react-app react-pagination

npm >= 5.2

اگر از NPM نسخه 5.2 یا بالاتر استفاده می کنید آن npx باینری اضافی ارسال می کند. با استفاده از npx باینری نیازی به نصب create-react-app به صورت سراسری ندارید. با این فرمان ساده می شود یک برنامه React جدید ایجاد کرد:

npx create-react-app react-pagination

نصب

در گام بعد، وابستگی هایی که برای برنامه خود لازم داریم را نصب می کنیم. فرمان زیر را برای نصب وابستگی های مورد نیاز اجرا کنید.

yarn add bootstrap prop-types react-flags countries-api
yarn add -D npm-run-all node-sass-chokidar

node-sass-chokidar را به عنوان وابستگی به توسعه برای برنامه ای که مارا قادر به استفاده از SASS می کند نصب کرده ایم . حال فهرست src را باز کنید و پسوند همه فایل های .css را به  .scss تغییر دهید. در ادامه فایل های .css مورد نیاز با node-sass-chokidar کامپایل می شوند.

تغییر اسکریپت های npm

فایل package.json را ویرایش کرده و بخش scripts را اصلاح کنید تا مانند زیر شود:

"scripts": {
  "start:js": "react-scripts start",
  "build:js": "react-scripts build",
  "start": "npm-run-all -p watch:css start:js",
  "build": "npm-run-all build:css build:js",
  "test": "react-scripts test --env=jsdom",
  "eject": "react-scripts eject",
  "build:css": "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/",
  "watch:css": "npm run build:css && node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/ --watch --recursive"
}

وارد کردن  Bootstrap CSS

 بسته bootstrap را برای برنامه خود نصب می کنیم چون به سبک (طراحی) پیش فرض نیاز داریم. همچنین از استایل های کامپوننت صفحه بندی Bootstrap استفاده می کنیم  برای وارد کردن Bootstrap در برنامه، فایل src/index.js را ویرایش کرده و خط زیر را پیش از هر جمله import اضافه می کنیم.

import "bootstrap/dist/css/bootstrap.min.css";

نصب (تنظیم) آیکون های پرچم (Flag)

react-flags را به عنوان ضمیمه برنامه خود نصب کردیم. برای دسترسی به آیکون های پرچم باید تصاویر آیکون هارا در دایرکتوری public برنامه خود کپی کنیم. فرمان های زیر را در ترمینال خود اجرا کنید تا آیکون های پرچم کپی شوند.

mkdir -p public/img
cp -R node_modules/react-flags/vendor/flags public/img

 اگر از سیستم عامل ویندوز استفاده می کنید، به جای آن فرمان های زیر را اجرا کنید:

mkdir \public\img
xcopy \node_modules\react-flags\vendor\flags \public\img /s /e

دایرکتوری  componentها

کامپوننت های React  زیر را برای برنامه خود استفاده می کنیم.

CountryCard- این در حقیقت نام، منطقه و پرچم کشور داده شده را ارائه می دهد.

Pagination - این حاوی منطق کامل ایجاد،‌ ارائه و چرخش صفحات در کنترل صفحه بندی است.

ادامه دهید و فهرست components  را داخل دایرکتوری src برنامه ایجاد کنید تا همه کامپوننت ها در آن جای گیرند.

شروع برنامه

  با اجرای دستور زیر با yarn برنامه را شروع کنید:

yarn start

حال برنامه آغاز شده است و می شود توسعه را آغاز کرد. توجه داشته باشید که تب مرورگر با عملکرد live reloading  برای شما باز شده تا درحین توسعه اگر تغییراتی بوجود آوردید همزمان تاثیرش را ببینید. در این مرحله، نمای برنامه باید مانند اسکرین شات زیر باشد.

کامپوننت CountryCard

فایل جدید CountryCard.js را در فهرست  src/components ایجاد کرده و کد زیر را اضافه کنید.

import React from 'react';
import PropTypes from 'prop-types';
import Flag from 'react-flags';

const CountryCard = props => {
  const { cca2: code2 = '', region = null, name = {}  } = props.country || {};

  return (
    <div className="col-sm-6 col-md-4 country-card">
      <div className="country-card-container border-gray rounded border mx-2 my-3 d-flex flex-row align-items-center p-0 bg-light">

        <div className="h-100 position-relative border-gray border-right px-2 bg-white rounded-left">

          <Flag country={code2} format="png" pngSize={64} basePath="./img/flags" className="d-block h-100" />

        </div>

        <div className="px-3">

          <span className="country-name text-dark d-block font-weight-bold">{ name.common }</span>

          <span className="country-region text-secondary text-uppercase">{ region }</span>

        </div>

      </div>
    </div>
  )
}

CountryCard.propTypes = {
  country: PropTypes.shape({
    cca2: PropTypes.string.isRequired,
    region: PropTypes.string.isRequired,
    name: PropTypes.shape({
      common: PropTypes.string.isRequired
    }).isRequired
  }).isRequired
};

export default CountryCard;

کامپوننت CountryCard به یک پراپرتی country نیاز دارد که حاوی داده هایی درباره کشوری است که باید ارائه شود. همانطور که در propTypes مربوط به کامپوننت CountryCard می بینید، شی country  باید حاوی داده های زیر باشد:

cca2- کد کشوری دورقمی

region - منطقه کشور برای مثال آفریقا

name.common -  نام مرسوم کشور برای مثال نیجریه

در اینجا یک شی کشور نمونه داریم:

{
  cca2: "NG",
  region: "Africa",
  name: {
    common: "Nigeria"
  }
}

همچنین توجه کنید چگونه پرچم کشور را با استفاده از بسته react-flags رندر می کنیم.

کامپوننت صفحه بندی

فایل جدید CountryCard.js را در دایرکتوری  src/components ایجاد کرده و کد زیر را اضافه کنید.

import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';

class Pagination extends Component {

  constructor(props) {
    super(props);
    const { totalRecords = null, pageLimit = 30, pageNeighbours = 0 } = props;

    this.pageLimit = typeof pageLimit === 'number' ? pageLimit : 30;
    this.totalRecords = typeof totalRecords === 'number' ? totalRecords : 0;

    // pageNeighbours can be: 0, 1 or 2
    this.pageNeighbours = typeof pageNeighbours === 'number'
      ? Math.max(0, Math.min(pageNeighbours, 2))
      : 0;

    this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);

    this.state = { currentPage: 1 };
  }

}

Pagination.propTypes = {
  totalRecords: PropTypes.number.isRequired,
  pageLimit: PropTypes.number,
  pageNeighbours: PropTypes.number,
  onPageChanged: PropTypes.func
};

export default Pagination;

همانطور که در شی  propTypes مشخص شد کامپوننت  Pagination چهار prop خاص می گیرد.

totalRecords- به تعداد کل رکوردهایی که باید صفحه بندی شوند اشاره می کند.

لازم است.

pageLimit - به تعداد رکوردهایی که در هر صفحه نشان داده می شود اشاره دارد. اگر مشخص نشده باشد همانطور که در constructor() تعریف شد پیش فرض آن که ۳۰ است قرار می گیرد.

pageNeighbours- به تعداد شماره صفحات اضافی اشاره می کند تا هر طرف صفحه فعلی را نشان دهد. حداقل مقدار ۰و حداکثر ۲ است. اگر مشخص نشده باشد همانطور که در constructor() تعریف شد پیش فرض آن که 0 است قرار می گیرد. تصویر زیر اثر مقادیر متفاوت پراپ pageNeighbours را نمایش می دهد:

onPageChanged  تابعی است که تنها وقتی با داده های وضعیت صفحه بندی فعلی فراخوانی می شود که صفحه فعلی تغییر کند.

در تابع constructor() کل صفحات را به صورت زیر محاسبه می کنیم:

this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);

توجه داشته باشید که در اینجا از Math.ceil() استفاده می کنیم تا مطمئن شویم مقداری که برای کل صفحات بدست می آید integer  است. همچنین باعث می شود که رکورد های اضافی در آخرین صفحه ثبت شوند، خصوصا در مواردی که تعداد رکوردهای اضافی کمتر از تعداد رکوردهایی است که در هر صفحه نشان داده می شود.

 سرانجام، پراپرتی currentPage را با ۱ مقدار دهی اولیه می کنیم.ما به این حالت برای فهمیدن صفحه ای که در حال حاضر فعال است نیاز داریم.

سپس، ادامه داده و متدی برای تولید تعداد صفحات ایجاد می کنیم. کامپوننت  Pagination  را همانطور که در کد نمایش داده شده تغییر دهید:

const LEFT_PAGE = 'LEFT';
const RIGHT_PAGE = 'RIGHT';

/**
 * Helper method for creating a range of numbers
 * range(1, 5) => [1, 2, 3, 4, 5]
 */
const range = (from, to, step = 1) => {
  let i = from;
  const range = [];

  while (i <= to) {
    range.push(i);
    i += step;
  }

  return range;
}

class Pagination extends Component {

  /**
   * Let's say we have 10 pages and we set pageNeighbours to 2
   * Given that the current page is 6
   * The pagination control will look like the following:
   *
   * (1) < {4 5} [6] {7 8} > (10)
   *
   * (x) => terminal pages: first and last page(always visible)
   * [x] => represents current page
   * {...x} => represents page neighbours
   */
  fetchPageNumbers = () => {

    const totalPages = this.totalPages;
    const currentPage = this.state.currentPage;
    const pageNeighbours = this.pageNeighbours;

    /**
     * totalNumbers: the total page numbers to show on the control
     * totalBlocks: totalNumbers + 2 to cover for the left(<) and right(>) controls
     */
    const totalNumbers = (this.pageNeighbours * 2) + 3;
    const totalBlocks = totalNumbers + 2;

    if (totalPages > totalBlocks) {

      const startPage = Math.max(2, currentPage - pageNeighbours);
      const endPage = Math.min(totalPages - 1, currentPage + pageNeighbours);

      let pages = range(startPage, endPage);

      /**
       * hasLeftSpill: has hidden pages to the left
       * hasRightSpill: has hidden pages to the right
       * spillOffset: number of hidden pages either to the left or to the right
       */
      const hasLeftSpill = startPage > 2;
      const hasRightSpill = (totalPages - endPage) > 1;
      const spillOffset = totalNumbers - (pages.length + 1);

      switch (true) {
        // handle: (1) < {5 6} [7] {8 9} (10)
        case (hasLeftSpill && !hasRightSpill): {
          const extraPages = range(startPage - spillOffset, startPage - 1);
          pages = [LEFT_PAGE, ...extraPages, ...pages];
          break;
        }

        // handle: (1) {2 3} [4] {5 6} > (10)
        case (!hasLeftSpill && hasRightSpill): {
          const extraPages = range(endPage + 1, endPage + spillOffset);
          pages = [...pages, ...extraPages, RIGHT_PAGE];
          break;
        }

        // handle: (1) < {4 5} [6] {7 8} > (10)
        case (hasLeftSpill && hasRightSpill):
        default: {
          pages = [LEFT_PAGE, ...pages, RIGHT_PAGE];
          break;
        }
      }

      return [1, ...pages, totalPages];

    }

    return range(1, totalPages);

  }

}

در این جا دو ثابت LEFT_PAGE و  RIGHT_PAGE را تعریف میکنیم . این ثوابت برای نشان دادن نقاطی که ما کنترل های صفحه را برای حرکت به سمت چپ و راست استفاده میکنیم ، استفاده میشود

همچنین تابع ()range را به منظور تولید رنج اعداد تعریف کرده ایم . اگر شما از لایبراری مثل Loadash استفاده میکنید ، میتوانید از تابع _.range() که توسط Loadash فراهم شده است استفاده کنید . کد زیر تفاوت بین تابع rang() ای که ما تعریف کرده ایم و آن تابعی که در Lodash تعریف شده است را نشان میدهد

range(1, 5); // returns [1, 2, 3, 4, 5]
_.range(1, 5); // returns [1, 2, 3, 4]

سپس، متد fetchPageNumbers() در کلاس Pagination تعریف می کنیم. این روش منطق اصلی برای تولید شماره صفحه که در کنترل صفحه بندی نمایش داده می شود را اداره می کند.

میخواهیم اولین و آخرین صفحه همواره قابل مشاهده باشد.

ابتدا دو متغیر انتخاب می کنیم.

totalNumbers نشان دهنده تعداد کلی صفحات است که در کنترل نمایش داده می شود.

totalBlocks  نشان دهنده تعداد کلی صفحات است که به اضافه دو بلوک اضافی برای شاخص های چپ و راست صفحه نشان داده می شود.

اگرtotalPages بزرگتر از totalBlocks نبود،‌رنجی از شماره ها را از ۱ تا totalPages برمی گردانیم.

در غیراینصورت، آرایه ای از شماره صفحات با LEFT_PAGE و RIGHT_PAGE بازمی گردانیم تا مرحله ای که به ترتیب در سمت چپ و راست ریخته شوند.

توجه کنید که چطور صفحه بندی تضمین می کند که صفحه اول و صفحه آخر همیشه قابل مشاهده باشند.

کنترل های صفحه چپ و راست در داخل نمایش داده می شوند.

حالا ادامه میدهیم و متد render() را اضافه می کنیم تا بتوانیم کنترل صفحه بندی را اضافه کنیم.

کامپوننت  Pagination  را همانطور که در کد نمایش داده شده تغییر دهید:

class Pagination extends Component {

render() {

    if (!this.totalRecords || this.totalPages === 1) return null;

    const { currentPage } = this.state;
    const pages = this.fetchPageNumbers();

    return (
      <Fragment>
        <nav aria-label="Countries Pagination">
          <ul className="pagination">
            { pages.map((page, index) => {

              if (page === LEFT_PAGE) return (
                <li key={index} className="page-item">
                  <a className="page-link" href="#" aria-label="Previous" onClick={this.handleMoveLeft}>
                    <span aria-hidden="true">&laquo;</span>
                    <span className="sr-only">Previous</span>
                  </a>
                </li>
              );

              if (page === RIGHT_PAGE) return (
                <li key={index} className="page-item">
                  <a className="page-link" href="#" aria-label="Next" onClick={this.handleMoveRight}>
                    <span aria-hidden="true">&raquo;</span>
                    <span className="sr-only">Next</span>
                  </a>
                </li>
              );

              return (
                <li key={index} className={`page-item${ currentPage === page ? ' active' : ''}`}>
                  <a className="page-link" href="#" onClick={ this.handleClick(page) }>{ page }</a>
                </li>
              );

            }) }

          </ul>
        </nav>
      </Fragment>
    );

  }

}

در اینجا، تعداد صفحات  array را با فراخوانی متد fetchPageNumbers() که بیشتر ایجاد کردیم تولید می کنیم. سپس شماره هر صفحه را با استفاده از Array.prototype.map() رندر می کنیم. توجه کنید که ما هندلر های رویداد کلیک را روی هر شماره صفحه رندر شده گذاشته ایم تا کلیک ها را مدیریت کنیم.

همچنین توجه داشته باشید که اگر پراپرتی totalRecords  به درستی به کامپوننت Pagination داده نشود یا در مواردی که تنها یک صفحه  وجود دارد ، کنترل صفحه بندی رندر نمی شود.

سرانجام متدهای هندلر رویداد را تعریف می کنیم. کامپوننت  Pagination  را همانطور که در کد نمایش داده شده تغییر دهید:

class Pagination extends Component {

  componentDidMount() {
    this.gotoPage(1);
  }

  gotoPage = page => {
    const { onPageChanged = f => f } = this.props;

    const currentPage = Math.max(0, Math.min(page, this.totalPages));

    const paginationData = {
      currentPage,
      totalPages: this.totalPages,
      pageLimit: this.pageLimit,
      totalRecords: this.totalRecords
    };

    this.setState({ currentPage }, () => onPageChanged(paginationData));
  }

  handleClick = page => evt => {
    evt.preventDefault();
    this.gotoPage(page);
  }

  handleMoveLeft = evt => {
    evt.preventDefault();
    this.gotoPage(this.state.currentPage - (this.pageNeighbours * 2) - 1);
  }

  handleMoveRight = evt => {
    evt.preventDefault();
    this.gotoPage(this.state.currentPage + (this.pageNeighbours * 2) + 1);
  }

}

متد  gotoPage() را تعریف می کنیم که state را تعریف می کند و currentPage را با صفحه مشخص شده تنظیم می کند. و تضمین می کند که آرگومان pageمقدار حداقلی ۱ و مقدارحداکثری تعداد کل صفحات را دارد.

سرانجام تابع onPageChanged() را که به عنوان پراپ پاس داده شده است با داده هایی که حالت صفحه بندی جدیدی را نشان می دهند فراخوانی می کند.

وقتی این کامپوننت راه اندازی می شود ما در حقیقت با فراخوانی this.gotoPage(1) همانطور که در متد componentDidMount() نشان داده شده است به اولین صفحه می رویم.

توجه کنید که چگونه از (this.pageNeighbours * 2) درhandleMoveLeft() و handleMoveRight() برای حرکت کردن در صفحات به ترتیب به راست یا چپ براساس شماره صفحه فعلی استفاده می کنیم.

این نمایشی است که توانستیم بدست آوریم:

کامپوننت برنامه

 فایل App.js را در دایرکتوری src  اضافه می کنیم. فایل App.js  باید مانند زیر باشد:

import React, { Component } from 'react';
import Countries from 'countries-api';
import './App.css';

import Pagination from './components/Pagination';
import CountryCard from './components/CountryCard';

class App extends Component {

  state = { allCountries: [], currentCountries: [], currentPage: null, totalPages: null }

  componentDidMount() {
    const { data: allCountries = [] } = Countries.findAll();
    this.setState({ allCountries });
  }

  onPageChanged = data => {
    const { allCountries } = this.state;
    const { currentPage, totalPages, pageLimit } = data;

    const offset = (currentPage - 1) * pageLimit;
    const currentCountries = allCountries.slice(offset, offset + pageLimit);

    this.setState({ currentPage, currentCountries, totalPages });
  }

}

export default App;

در اینجا کامپوننک App را با صفات زیر مقدار دهی می کنیم:

 allCountries - :آرایه ای از همه کشورهای موجود در برنامه.

: currentCountries - آرایه ای از همه کشورهایی که در صفحه فعال (همان صفحه ای که کاربر میبیند) نشان داده می شوند.

 currentPage - :شماره صفحه فعال فعلی. با صفر مقدار دهی می شود.

-totalPages:تعداد کل صفحات برای همه رکوردهای کشور با صفر مقدار دهی می شود.

سپس در متد componentDidMount() همه کشورهای جهان را بافراخوانی Countries.findAll() و استفاده از countries-api واکشی می نماییم.

سپس حالت برنامه را به روز رسانی می کنیم و allCountries  را تنظیم می کنیم که همه کشورهای جهان را در برد بگیرد.

سرانجام متد onPageChanged() را تعریف می کنیم که هرگاه صفحه جدیدی را از کنترل صفحه بندی می پیماییم  فراخوانده می شود. این متد به پراپ onPageChanged از کامپوننت صفحه بندی ارسال می شود. در این متد دوخط هست که ارزش توجه دارد.

اولین خط :

const offset = (currentPage - 1) * pageLimit;

مقدار offset به ایندکس شروع کننده برای واکشی رکوردها ی مربوط به صفحه فعلی اشاره می کند. استفاده ازcurrentPage - 1) ) تضمین می کند که آفست مبتنی بر صفر است. برای مثال در هر صفحه ۲۵ رکورد نشان می دهیم اما در حال حاضر ۵ رکورد نمایش داده می شود. پس  استoffset  ((5 - 1) * 25 = 100)  

اگر برای مثال رکوردها را براساس تقاضا واکشی کنیم، این نمونه ای از کوئری SQL است که طرز استفاده از آفست را نشان می دهد. از آنجایی که رکورد ها را از پایگاه داده یا منبع خارجی واکشی نمی کنیم، به روشی نیاز داریم تا قطعه داده مورد نیازی که برای صفحه فعلی نشان داده می شود را استخراج نماییم.

SELECT * FROM `countries` LIMIT 100, 25

 دومین خط:

const currentCountries = allCountries.slice(offset, offset + pageLimit);

توجه کنید که متد Array.prototype.slice()   برای استخراج قطعه رکوردها از allCountries  با ارسال offset به عنوان  ایندکس شروع کننده برای برش و  (offset + pageLimit) به عنوان شاخص استفاده می کنیم پیش از اینکه برش به پایان برسد.

واکشی رکوردها در برنامه های واقعی

برای اینکه این مقاله تا جای ممکن ساده باشد رکوردها را از منبع خارجی واکشی نکردیم. در یک برنامه واقعی احتمالا رکوردهایی را از پایگاه داده یا یک API واکشی می کنید.

منطق واکشی رکوردها به راحتی به متد onPageChanged()از کامپوننت App می رود.

اگر یک API ساختگی داشته باشیم. /api/countries?page={current_page}&limit={page_limit}

قطعه کد زیر نشان می دهد که چگونه می شود کشورها را از API با استفاده از بسته ‏axios HTTP واکشی کرد:

onPageChanged = data => {
 const { currentPage, totalPages, pageLimit } = data;

 axios.get(`/api/countries?page=${currentPage}&limit=${pageLimit}`)
   .then(response => {
     const currentCountries = response.data.countries;
     this.setState({ currentPage, currentCountries, totalPages });
   });
}

حال ادامه می دهیم و کامپوننت App را با اضافه کردن متد render()  به پایان می بریم. کامپوننت  app  را همانطور که درزیر نمایش داده شده تغییر می دهیم:

class App extends Component {

  // other methods here ...

  render() {

    const { allCountries, currentCountries, currentPage, totalPages } = this.state;
    const totalCountries = allCountries.length;

    if (totalCountries === 0) return null;

    const headerClass = ['text-dark py-2 pr-4 m-0', currentPage ? 'border-gray border-right' : ''].join(' ').trim();

    return (
      <div className="container mb-5">
        <div className="row d-flex flex-row py-5">

          <div className="w-100 px-4 py-5 d-flex flex-row flex-wrap align-items-center justify-content-between">
            <div className="d-flex flex-row align-items-center">

              <h2 className={headerClass}>
                <strong className="text-secondary">{totalCountries}</strong> Countries
              </h2>

              { currentPage && (
                <span className="current-page d-inline-block h-100 pl-4 text-secondary">
                  Page <span className="font-weight-bold">{ currentPage }</span> / <span className="font-weight-bold">{ totalPages }</span>
                </span>
              ) }

            </div>

            <div className="d-flex flex-row py-4 align-items-center">
              <Pagination totalRecords={totalCountries} pageLimit={18} pageNeighbours={1} onPageChanged={this.onPageChanged} />
            </div>
          </div>

          { currentCountries.map(country => <CountryCard key={country.cca3} country={country} />) }

        </div>
      </div>
    );

  }

}

متدrender() کاملا سرراست است.

تعداد کل کشورها را رندر می کنیم،‌صفحه فعلی، تعداد کل صفحات، کنترل<Pagination> و سپس CountryCard>> برای هر کشور در صفحه فعلی.

توجه کنید که متد onPageChanged()که بیشتر تعریف کردیم را به پراپ onPageChanged از کنترل < Pagination > ارسال کردیم.

این برای ثبت تغییرات صفحه از کامپوننت Pagination بسیار مهم است. همچنین توجه داشته باشید که ۱۸ کشور را در هر صفحه نشان می دهیم.

در این مرحله، برنامه باید مانند اسکرین شات زیر باشد.

لول آپ کردن با برخی استایل ها

توجه کرده اید که برخی کلاس های سفارشی را به اجزایی که قبلا ایجاد کرده ایم اضافه می کنیم. ادامه می دهیم و برخی قوانین طراحی مربوط به کلاس هایی که در فایل src/App.scss وجود دارد تعریف می کنیم. فایل App.scss  باید مانند قطعه کد زیر باشد:

/* Declare some variables */
$base-color: #ced4da;
$light-background: lighten(desaturate($base-color, 50%), 12.5%);

.current-page {
  font-size: 1.5rem;
  vertical-align: middle;
}

.country-card-container {
  height: 60px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}

.country-name {
  font-size: 0.9rem;
}

.country-region {
  font-size: 0.7rem;
}

.current-page,
.country-name,
.country-region {
  line-height: 1;
}

// Override some Bootstrap pagination styles
ul.pagination {
  margin-top: 0;
  margin-bottom: 0;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);

  li.page-item.active {
    a.page-link {
      color: saturate(darken($base-color, 50%), 5%) !important;
      background-color: saturate(lighten($base-color, 7.5%), 2.5%) !important;
      border-color: $base-color !important;
    }
  }

  a.page-link {
    padding: 0.75rem 1rem;
    min-width: 3.5rem;
    text-align: center;
    box-shadow: none !important;
    border-color: $base-color !important;
    color: saturate(darken($base-color, 30%), 10%);
    font-weight: 900;
    font-size: 1rem;

    &:hover {
      background-color: $light-background;
    }
  }
}

پس از اضافه کردن استایل ها حالا برنامه باید مانند اسکرین شات زیر باشد: