One place for hosting & domains

      personalizada

      Cómo crear paginación personalizada con React


      Introducción

      A menudo tenemos que crear aplicaciones web en las que necesitamos buscar grandes conjuntos de registros de datos de un servidor remoto, API o una base de datos. Si está creando un sistema de pago, por ejemplo, podría ser buscar miles de transacciones. Si es una aplicación de redes sociales, podría ser buscar muchos comentarios de usuarios, perfiles o actividades. Sea cual sea la aplicación, existen varias soluciones para presentar los datos de una forma que no abrume al usuario final que interactúa con la aplicación.

      Un método para administrar grandes conjuntos de datos es usar la paginación. La paginación funciona de forma efectiva cuando ya conoce el tamaño del conjunto de datos (el número total de registros en la base de datos). Segundo, solo carga el lote requerido de datos del conjunto total de datos según la interacción del usuario final con el control de paginación. Esta es la técnica usada a la hora de mostrar resultados de la búsqueda en Google.

      En este tutorial, aprenderá cómo crear un componente de paginación personalizado con React para paginar grandes conjuntos de datos. Creará una vista paginada de los países del mundo, un conjunto de datos con un tamaño conocido.

      Aquí tiene una demostración de lo que va a crear en este tutorial:

      Captura de pantalla de la aplicación de demostración, que muestra los países del mundo

      Requisitos previos

      Para completar este tutorial, necesitará lo siguiente:

      Este tutorial se verificó con Node v14.2.0, npm v6.14.4, react v16.13.1 y react-scripts v3.4.1.

      Paso 1: Configurar el proyecto

      Comience una nueva aplicación React usando el comando create-react-app. Puede ponerle el nombre que desee a la aplicación, pero este tutorial la llamará react-pagination:

      • npx create-react-app react-pagination

      A continuación, instalará las dependencias necesarias para su aplicación. Primero, utilice la ventana del terminal para navegar al directorio del proyecto:

      Ejecute el siguiente comando para instalar las dependencias necesarias:

      • 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

      Esto instalará bootstrap, prop-types, react-flags, countries-api y node-sass.

      Instaló el paquete bootstrap como dependencia para su aplicación, ya que necesitará algunos estilos predeterminados. También usará los estilos del componente de pagination de Bootstrap.

      Para incluir Bootstrap en la aplicación, edite el archivo src/index.js:

      Y añada la siguiente línea antes de las demás declaraciones import:

      src/index.js

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

      Ahora los estilos de Bootstrap estarán disponibles para su aplicación.

      También instaló react-flags como dependencia para su aplicación. Para obtener acceso a los iconos de banderas de su aplicación, necesitará copiar las imágenes de iconos al directorio public de su aplicación.

      Cree un directorio img en su directorio public:

      Copie los archivos de imágenes en flags a img:

      • cp -R node_modules/react-flags/vendor/flags public/img

      Esto proporciona una copia de todas las imágenes react-flag para su aplicación.

      Ahora que ha incluido algunas dependencias, inicie la aplicación ejecutando el siguiente comando con npm desde el directorio del proyecto react-pagination.

      Ahora que ha iniciado la aplicación, puede comenzar el desarrollo. Observe que se abrió una ventana del navegador con la funcionalidad live-reloading para mantener la sincronización con la aplicación a medida que desarrolla.

      En este momento, la vista de la aplicación debería parecerse a la siguiente captura de pantalla:

      Vista inicial - Bienvenido a la pantalla de React

      Ahora está listo para comenzar a crear componentes.

      Paso 2: Crear el componente CountryCard

      En este paso, creará el componente CountryCard. El componente CountryCard renderiza el nombre, región y bandera de un país concreto.

      Primero, vamos a crear un directorio components en el directorio src:

      A continuación, cree un nuevo archivo CountryCard.js en el directorio src/components:

      • nano src/components/CountryCard.js

      Luego, añádale el siguiente snippet:

      src/components/CountryCard.js

      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;
      

      El componente CountryCard requiere una propiedad country que contiene los datos sobre el país que va a renderizar. Como se ha visto en los propTypes para el componente CountryCard, el objeto prop country debe contener los siguientes datos:

      • cca2: código del país de 2 dígitos
      • region: la región del país (por ejemplo, “África”)
      • name.common: el nombre común del país (por ejemplo “Nigeria”)

      Aquí hay un objetivo de país de muestra:

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

      Además, observe cómo renderiza la bandera del país usando el paquete react-flags. Puede comprobar la documentación react-flagspara obtener más información sobre los props necesarios y cómo usar el paquete.

      Ahora ha completado un componente CountryCard individual. Finalmente, usará CountryCard varias veces para mostrar diferentes banderas e información del país en su aplicación.

      En este paso, creará el componente Pagination. El componente Pagination contiene la lógica para crear, renderizar y cambiar páginas en el control de paginación.

      A continuación, cree un nuevo archivo Pagination.js en el directorio src/components:

      • nano src/components/Pagination.js

      Luego, añádale el siguiente snippet:

      src/components/Pagination.js

      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;
      

      El componente Pagination puede tomar cuatro propiedades especiales como se especifica en el objeto propTypes.

      • onPageChanged es una función invocada con datos del estado de paginación actual solo cuando la página actual cambia.
      • totalRecords indica el número total de registros que se paginarán. Es obligatorio.
      • pageLimit indica el número de registros que se mostrarán por página. Si no se especifica, vuelve al valor predeterminado de 30 como se ha definido en el constructor().
      • pageNeighbours indica el número de números de páginas adicionales para mostrar en cada lado de la página actual. El valor mínimo es 0 y el valor máximo es 2. Si no se especifica, vuelve al valor 0, como se definió en constructor().

      La siguiente imagen ilustra el efecto de los diferentes valores del prop pageNeighbours:

      Ilustración de Page Neighbours

      En la función constructor(), calcule las páginas totales de la siguiente manera:

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

      Observe que aquí utiliza Math.ceil() para garantizar que obtiene un valor entero para el número total de páginas. Esto también garantiza que el excedente de registros se captura en la última página, especialmente en los casos en que el número de registros que sobran es menor que el número de registros que se mostrarán por página.

      Finalmente, inició el estado con la propiedad currentPage configurada en 1. Necesita esta propiedad de estado para realizar un seguimiento interno de la página actualmente activa.

      A continuación, creará el método para generar los números de página.

      Tras las importaciones pero antes de la clase Pagination, añada las siguientes constantes y una función range:

      src/components/Pagination.js

      // ...
      
      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;
      }
      

      En la clase Pagination, tras el constructor, añada el siguiente método fetchPageNumbers:

      src/components/Pagination.js

      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);
        }
      }
      

      Aquí, primero define dos constantes: LEFT_PAGE y RIGHT_PAGE. Estas constantes se usarán para indicar puntos en los que tiene controles de página para moverse a la izquierda y derecha respectivamente.

      También definió una función de ayuda range() que puede ayudar a generar intervalos de números.

      Nota: Si utiliza una biblioteca de utilidades como Lodash en su proyecto, puede usar la función _.range() proporcionada por Lodash. El siguiente snippet de código muestra la diferencia entre la función range() que acaba de definir y la de Lodash:

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

      A continuación, definió el método fetchPageNumbers() en la clase Pagination. Este método gestiona la lógica principal para generar los números de página que se mostrarán en el control de paginación. Quiere que la primera página y la última página siempre estén visibles.

      Primero, ha definido un par de variables. totalNumbers representa los números de página en total que se mostrarán en el control. totalBlocks representa el número total de páginas que se mostrará más dos bloques adicionales para los indicadores de página izquierda y derecha.

      Si totalPages no es mayor que totalBlocks, devuelve un intervalo de número desde 1 a totalPages. De lo contrario, devolverá la matriz de números de página, con LEFT_PAGE y RIGHT_PAGE en los lugares donde tenga páginas que se reparten a la izquierda y la derecha, respectivamente.

      Sin embargo, observe que su control de paginación garantiza que la primera página y la última página siempre estén visibles. Los controles de página izquierda y derecha aparecen hacia dentro.

      Ahora, añadirá el método render() para permitirle renderizar el control de paginación.

      En la clase Pagination, tras el método constructor y fetchPageNumbers, añada el siguiente método render:

      src/components/Pagination.js

      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="https://www.digitalocean.com/community/tutorials/#" 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="https://www.digitalocean.com/community/tutorials/#" 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="https://www.digitalocean.com/community/tutorials/#" onClick={ this.handleClick(page) }>{ page }</a>
                      </li>
                    );
      
                  }) }
      
                </ul>
              </nav>
            </Fragment>
          );
        }
      }
      

      Aquí, genera la matriz de números de página invocando el método fetchPageNumbers() que creó anteriormente. A continuación renderiza cada número de página usando Array.prototype.map(). Observe que registra los controladores de eventos de clic en cada número de página renderizada para gestionar los clics.

      Además, observe que el control de paginación no se renderizará si el prop totalRecords no se pasó correctamente al componente Pagination o en los casos en que solo haya 1 página.

      Finalmente, definirá los métodos del controlador de eventos.

      En la clase Pagination, tras el constructor y el método fetchPageNumbers y el método render, añada lo siguiente:

      src/components/Pagination.js

      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);
        }
      }
      

      Define el método gotoPage() que modifica el estado y establece currentPage a la página especificada. Garantiza que el argumento page tiene un valor mínimo de 1 y un valor máximo del número total de páginas. Finalmente invoca la función onPageChanged() que se pasó como prop, con datos que indican el nuevo estado de paginación.

      Cuando se monta el componente, va a la primera página invocando this.gotoPage(1) como se muestra en el método de ciclo de vida componentDidMount().

      Observe cómo usa (this.pageNeighbours * 2) en handleMoveLeft() y handleMoveRight() para deslizar los números de página a la izquierda y a la derecha, respectivamente, según el número de página actual.

      Aquí tiene una demostración de la interacción del movimiento izquierdo a derecho.

      Movimiento izquierda-derecha de la interacción

      Ahora ha completado el componente Pagination. Los usuarios podrá interactuar con los controles de navegación en este componente para mostrar diferentes páginas de banderas.

      Paso 4: Crear el componente App

      Ahora que tiene un componente CountryCard y Pagination, puede usarlos en su componente App.

      Modifique el archivo App.js en el directorio src:

      Sustituya el contenido de App.js con las siguientes líneas de código:

      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;
      

      Aquí inicia el estado del componente App con los siguientes atributos:

      • allCountries - Esta es una matriz de todos los países en su aplicación. Iniciado a una matriz vacía ([]).
      • currentCountries - Esta es una matriz de todos los países que se mostrarán en la página activa actualmente. Iniciado a una matriz vacía ([]).
      • currentPage - El número de página de la página activa actualmente. Iniciado a null.
      • totalPages - El número de páginas en total para todos los registros de país. Iniciado a null.

      A continuación, en el método de ciclo de vida componentDidMount(), busca todos los países del mundo usando el paquete countries-api invocando Countries.findAll(). A continuación, actualiza el estado de la aplicación, configurando allCountries para que contenga todos los países del mundo. Puede consultar la documentación de countries-api para obtener más información sobre el paquete.

      Finalmente, definió el método onPageChanged(), que invocaremos cada vez que navegue a una nueva página desde el control de paginación. Este método se pasará a la propiedad onPageChanged del componente Pagination.

      Hay dos líneas a las que vale la pena prestar atención en este método. La primera es esta línea:

      const offset = (currentPage - 1) * pageLimit;
      

      El valor offset indica el índice de inicio para buscar los registros para la página actual. Usar (currentPage - 1) garantiza que el offset se basa en cero. Digamos, por ejemplo, que está mostrando 25 registros por página, y actualmente está viendo la página 5. Luego el offset será (5 - 1) * 25 = 100).

      Por ejemplo, si está buscando registros bajo demanda desde una base de datos, esta es una consulta SQL de muestra para mostrarle cómo usar la posición inicial:

      SELECT * FROM `countries` LIMIT 100, 25
      

      Ya que no está buscando registros desde una base de datos o cualquier fuente externa, necesita una forma de extraer el bloque de registros requerido que se mostrará para la página actual.

      La segunda es esta línea:

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

      Aquí utiliza el método Array.prototype.slice() para extraer el bloque de registros requerido desde allCountries pasando offset como el índica de inicio para la porción y (offset + pageLimit) como el índice antes del cual finalizar la porción.

      Nota: En este tutorial no buscó registros de una fuente externa. En una aplicación real, probablemente estará buscando registros desde una base de datos o una API. La lógica para buscar los registros puede ir al método onPageChanged() del componente App.

      Digamos que tiene un endpoint de API ficticio /api/countries?page={current_page}&limit={page_limit}. El siguiente snippet muestra cómo buscar países bajo demanda desde la API usando el paquete HTTP de axios:

      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 });
          });
      }
      

      Ahora, puede terminar el componente App añadiendo el método render().

      En la clase App, pero tras componentDidMount y onPageChanged, añada el siguiente método render:

      src/App.js

      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>
          );
        }
      }
      

      En el método render(), renderiza el número total de países, la página actual, el número de páginas en total, el control <Pagination>, y luego la <CountryCard> para cada país en la página actual.

      Observe que ha pasado el método onPageChanged() que definió anteriormente para el prop onPageChanged del control <Pagination>. Esto es muy importante para capturar los cambios en la página desde el componente Pagination. También está mostrando 18 países por página.

      En este momento, la aplicación tendrá un aspecto similar a la siguiente captura de pantalla:

      Captura de pantalla de la aplicación con 248 países listados y números de página en la parte superior para avanzar por cada página

      Ahora tiene un componente App que muestra varios componentes CountryCard y un componente Pagination que desglosa los contenidos en páginas independientes. A continuación, explorará el estilo de su aplicación.

      Paso 5: Añadir estilos personalizados

      Quizá haya notado que estuvo añadiendo algunas clases personalizadas a los componentes que creó antes. Vamos a definir algunas reglas de estilo para esas clases en el archivo src/App.scss.

      El archivo App.scss tendrá el aspecto del siguiente snippet:

      src/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;
          }
        }
      }
      

      Modifique su archivo App.js para referenciar a App.scss en vez de a App.css.

      Nota: Para obtener más información sobre esto, consulte la documentación de Crear una aplicación React.

      src/App.js

      import React, { Component } from 'react';
      import Countries from 'countries-api';
      import './App.scss';
      import Pagination from './components/Pagination';
      import CountryCard from './components/CountryCard';
      

      Tras añadir los estilos, la aplicación ahora tendrá un aspecto similar al de la siguiente captura de pantalla:

      Captura de pantalla de la aplicación página 1 de 14 con estilos

      Ahora ha completado la aplicación con estilos personalizados adicionales. Puede usar estilos personalizados para modificar y mejorar cualquier estilo predeterminado proporcionado por las bibliotecas como Bootstrap.

      Conclusión

      En este tutorial, creó un widget de paginación personalizado en su aplicación React. Aunque no realizó invocaciones a ninguna API ni interactuar con cualquier backend en este tutorial, su aplicación puede demandar dichas interacciones. No está limitado al enfoque usado en este tutorial, puede ampliarlo como desee para adaptarse a los requisitos de su aplicación.

      Para ver el código fuente completo de este tutorial, consulte el repositorio build-react-pagination-demo en GitHub. También puede obtener una demostración en vivo de este tutorial en Code Sandbox.

      Si desea aprender más sobre React, eche un vistazo a nuestra serie Cómo crear código en React.js, o consulte nuestra página del tema React para ver ejercicios y proyectos de programación.



      Source link

      Como construir uma paginação personalizada com o React


      Introdução

      Muitas vezes, no desenvolvimento de aplicativos Web, precisamos buscar grandes conjuntos de registros de dados de um servidor remoto, API ou banco de dados. Se você estiver construindo um sistema de pagamento, por exemplo, ele poderia estar buscando milhares de transações. Se é um aplicativo de mídia social, poderia estar buscando muitos comentários de usuários, perfis ou atividades. Seja qual for o caso, existem diversas soluções para apresentar os dados de uma maneira que não sobrecarregue o usuário final que esteja interagindo com o aplicativo.

      Um dos métodos para lidar com grandes conjuntos de dados é usar a paginação. A paginação funciona de maneira eficaz quando o tamanho do conjunto de dados (o número total de registros no conjunto de dados) é conhecido previamente. Além disso, apenas a quantidade de dados necessária é carregada do conjunto total de dados com base na interação de usuários finais com o controle de paginação. Essa é a técnica usada na exibição de resultados na pesquisa do Google.

      Neste tutorial, veremos como criar um componente de paginação personalizada com o React para paginar grandes conjuntos de dados. Será construida uma visualização paginada dos países no mundo — um conjunto de dados com um tamanho conhecido.

      Aqui está uma demonstração daquilo que você construirá neste tutorial:

      Captura de tela do aplicativo de demonstração — exibindo os países no mundo

      Pré-requisitos

      Para completar este tutorial, você precisará de:

      • O Node instalado em sua máquina. Os passos para isso podem ser encontrados em Como instalar o Node.js e criar um ambiente de desenvolvimento local.
      • O pacote de linha de comando [create-react-app][`create-react-app] para criar um código boilerplate para seu aplicativo React. Caso esteja usando onpm < 5.2, pode ser necessário instalar ocreate-react-app` como uma dependência global.
      • Por fim, este tutorial assume que você já esteja familiarizado com o React. Se este não for o caso, verifique a série Como programar no React.js para aprender mais sobre o React.

      Este tutorial foi verificado com o Node v14.2.0, npm v6.14.4, react v16.13.1 e react-scripts v3.4.1.

      Passo 1 — Configurando o projeto

      Incie um novo aplicativo React usando o comando create-react-app. Sinta-se à vontade para dar o nome que preferir ao aplicativo, mas este tutorial irá chamá-lo de react-pagination:

      • npx create-react-app react-pagination

      Em seguida, você irá instalar as dependências necessárias para o seu aplicativo. Primeiro, use a janela do terminal para navegar até o diretório do projeto:

      Execute o seguinte comando para instalar as dependências necessárias:

      • 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

      Isso irá instalar o bootstrap, prop-types, react-flags, countries-api e o node-sass.

      O pacote bootstrap foi instalado como uma dependência para o seu aplicativo, pois você precisará de um estilo padrão. Além disso, você também irá utilizar estilos do componente pagination do Bootstrap.

      Para incluir o Bootstrap no aplicativo, edite o arquivo src/index.js:

      E adicione a linha a seguir antes das outras declarações import:

      src/index.js

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

      Agora, o estilo do Bootstrap estará disponível em todo o seu aplicativo.

      Além disso, o react-flags foi instalado como uma dependência para o seu aplicativo. Para obter acesso aos ícones de bandeira a partir do seu aplicativo, será necessário copiar as imagens dos ícones para o diretório public do seu aplicativo.

      Crie um diretório img em seu diretório public:

      Copie os arquivos de imagem em flags para img:

      • cp -R node_modules/react-flags/vendor/flags public/img

      Isso gera uma cópia de todas as imagens do react-flag para o seu aplicativo.

      Agora que você incluiu algumas dependências, inicie o aplicativo executando o comando a seguir com o npm a partir do diretório de projeto react-pagination:

      Agora que o aplicativo foi iniciado, o desenvolvimento pode começar. Observe que uma guia do navegador foi aberta para você com a funcionalidade de carregamento dinâmico para manter tudo sincronizado à medida em que você avança no desenvolvimento.

      Neste ponto, a visualização do aplicativo deve se assemelhar à seguinte captura de tela:

      Visualização inicial – tela de boas-vindas ao React

      Agora, tudo está pronto para começarmos a criar os componentes.

      Passo 2 — Criando o componente ContryCard

      Neste passo, você irá criar o componente CountryCard. O componente CountryCard processa o nome, região e bandeira de um determinado país.

      Primeiro, vamos criar um diretório components dentro do diretório src:

      Em seguida, crie um novo arquivo CountryCard.js dentro do diretório src/components:

      • nano src/components/CountryCard.js

      E adicione o seguinte código a ele:

      src/components/CountryCard.js

      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;
      

      O componente CountryCard exige uma propriedade country que contém os dados sobre o país a ser processado. Como visto nas propTypes para o componente CountryCard, a propriedade country deve conter os seguintes dados:

      • cca2 – Código de 2 dígitos do país
      • region – a região do país (por exemplo, “África”)
      • name.common – o nome comum do país (por exemplo, “Nigéria”)

      Aqui está um objeto de país de amostra:

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

      Além disso, note como a bandeira do país é renderizada usando o pacote react-flags. Verifique a documentação do react-flags para aprender mais sobre as propriedades exigidas e como usar o pacote.

      Agora, você finalizou a criação de um componente CountryCard individual. No final das contas, você usará os CountryCards várias vezes para exibir diferentes bandeiras e informações dos países em seu aplicativo.

      Passo 3 — Criando o componente Pagination

      Neste passo, você irá criar o componente Pagination. O componente Pagination contém a lógica para a construção, renderização e troca de páginas no controle de paginação.

      Crie um novo arquivo Pagination.js dentro do diretório src/components:

      • nano src/components/Pagination.js

      E adicione o seguinte código a ele:

      src/components/Pagination.js

      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;
      

      O componente Pagination pode receber quatro propriedades especiais assim como especificado no objeto propTypes.

      • onPageChanged é uma função chamada com dados do estado atual de paginação apenas quando a página atual muda.
      • totalRecords indica o número total de registros a serem paginados. Ele é exigido.
      • pageLimit indica o número de registros a serem exibidos por página. Caso não seja especificado, seu valor padrão é 30 conforme definido no constructor().
      • pageNeighbours indica a quantidade de números de página adicionais a se exibir em cada lado da página atual. O valor mínimo é 0, e o valor máximo é 2. Caso não seja especificado, o valor padrão é 0 conforme definido no constructor().

      A imagem a seguir ilustra o efeito de diferentes valores para a propriedade pageNeighbours:

      Ilustração de pageNeighbours

      Na função constructor(), você computa o total de páginas da seguinte forma:

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

      Observe que o Math.ceil() é usado aqui para garantir que seja obtido um valor inteiro para o número total de páginas. Isso também garante que os registros em excesso sejam capturados na última página, principalmente nos casos em que o número de registros em excesso seja inferior ao número de registros a serem exibidos por página.

      Por fim, você inicializou o estado com a propriedade currentPage definida em 1. Essa propriedade de estado é necessária para manter o controle interno da página ativa atual.

      Em seguida, você irá criar o método para gerar os números de página.

      Após os imports mas antes da classe Pagination, adicione as constantes a seguir e a função range:

      src/components/Pagination.js

      // ...
      
      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;
      }
      

      Na classe Pagination, após o constructor, adicione o seguinte método fetchPageNumbers:

      src/components/Pagination.js

      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);
        }
      }
      

      Aqui, você define duas constantes pela primeira vez: LEFT_PAGE e RIGHT_PAGE. Essas constantes serão usadas para indicar os pontos onde haverá controles de página para mover-se para a esquerda e direita, respectivamente.

      Você também definiu uma função auxiliar range() que pode ajudá-lo a gerar intervalos de números.

      Nota: se você usa uma biblioteca de utilidades como o Lodash em seu projeto, então pode usar a função _.range() disponibilizada pelo Lodash como alternativa. O código a seguir mostra a diferença entre a função range() que você acabou de definir e a do Lodash:

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

      Em seguida, você definiu o método fetchPageNumbers() na classe Pagination. Esse método processa a lógica principal para a geração dos números de página a serem exibidos no controle de paginação. É desejável que a primeira página e a última sejam estejam visíveis.

      Primeiro, algumas variáveis foram definidas. totalNumbers representa os números totais de páginas que serão exibidos no controle. totalBlocks representa os números totais de página a serem exibidos com o acréscimo de dois blocos adicionais para os indicadores esquerdo e direito de página.

      Se o totalPages não for maior que totalBlocks, você retorna um intervalo de números de 1 até totalPages. Caso contrário, você retorna a matriz de números de página, com LEFT_PAGE e RIGHT_PAGE em pontos onde existem páginas sendo despejadas para a esquerda e direita, respectivamente.

      No entanto, observe que o controle de paginação garante que a primeira página e a última estejam sempre visíveis. Os controles de página da esquerda e direita aparecem na parte de dentro.

      Agora, você irá adicionar o método render() para habilitar a renderização do controle de paginação.

      Na classe Pagination, após os métodosconstructor e fetchPageNumbers, adicione o seguinte método render:

      src/components/Pagination.js

      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="https://www.digitalocean.com/community/tutorials/#" 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="https://www.digitalocean.com/community/tutorials/#" 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="https://www.digitalocean.com/community/tutorials/#" onClick={ this.handleClick(page) }>{ page }</a>
                      </li>
                    );
      
                  }) }
      
                </ul>
              </nav>
            </Fragment>
          );
        }
      }
      

      Aqui, você gera o array de números de página chamando o método fetchPageNumbers() que você criou anteriormente. Em seguida, você renderiza cada número de página usando o Array.prototype.map(). Observe que manipuladores de eventos de clique foram registrados em cada número de página renderizada para manipular os cliques.

      Além disso, note que o controle de paginação não será renderizado caso a propriedade totalRecords não tenha sido passada corretamente para o componente Pagination, ou nos casos em que haja apenas 1 página.

      Por fim, você irá definir os métodos do manipulador de eventos.

      Na classe Pagination, após o constructor e métodos fetchPageNumbers e render, adicione o seguinte:

      src/components/Pagination.js

      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);
        }
      }
      

      Foi definido o método gotoPage() que modifica o estado e define o currentPage para a página especificada. Ele garante que o argumento page tenha um valor mínimo de 1 e um valor máximo do número total de páginas. Por fim, a função onPageChanged() que foi passada como uma propriedade é chamada, com dados indicando o novo estado de paginação.

      Quando o componente é montado, você vai até a primeira página chamando this.gotoPage(1) como mostrado no método de ciclo de vida componentDidMount().

      Observe como o (this.pageNeighbours * 2) foi usado em handleMoveLeft() e handleMoveRight() para deslizar os números de página para a esquerda e direita, respectivamente, com base no número atual de página.

      Aqui está uma demonstração da interação dos movimentos para a esquerde e direita.

      Interação da movimentação para a esquerda e direita

      Agora, você concluiu o componente Pagination. Os usuários serão capazes de interagir com os controles de navegação neste componente para exibir diferentes páginas de bandeiras.

      Passo 4 — Construindo o componente App

      Agora que você tem um componente CountryCard e um Pagination, use-os em seu componente App.

      Modifique o arquivo App.js no diretório src:

      Substitua o conteúdo de App.js pelas linhas de código a seguir:

      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;
      

      Aqui, inicializa-se o estado do componente App com os seguintes atributos:

      • allCountries – é uma matriz de todos os países em seu aplicativo. Inicializada como uma matriz vazia ([]).
      • currentCountries – é uma matriz de todos os países a serem exibidos na página atualmente ativa. Inicializada como uma matriz vazia ([]).
      • currentPage – é o número da página que está atualmente ativa. Inicializada como null.
      • totalPages – é o número total de páginas para todos os registros de país. Inicializada como null.

      Em seguida, no método de ciclo de vida componentDidMount(), busca-se todos os países usando o pacote countries-api invocando o Countries.findAll(). Depois disso, atualiza-se o estado do aplicativo, definindo allCountries para conter todos os países do mundo. Veja a documentação do countries-apipara aprender mais sobre o pacote.

      Por fim, definiu-se o método onPageChanged(), que será chamado toda vez que o usuário navegar para uma nova página a partir do controle de paginação. Esse método será passado para a propriedade onPageChanged do componente Pagination.

      Há duas linhas que merecem atenção nesse método. A primeira é esta:

      const offset = (currentPage - 1) * pageLimit;
      

      O valor offset (deslocamento) indica o índice de partida para buscar os registros para a página atual. Usar (currentPage - 1) garante que o deslocamento é baseado em zero. Vamos supor que, por exemplo, estejam sendo exibidos 25 registros por página, e o usuário esteja visualizado atualmente a página 5. Sendo assim, o offset será ((5-1) * 25 = 100).

      Exemplificando, caso esteja buscando registros sob demanda de um banco de dados, esta é uma consulta SQL de amostra para mostrar como o offset pode ser usado:

      SELECT * FROM `countries` LIMIT 100, 25
      

      Como você não está coletando registros de um banco de dados ou qualquer fonte externa, uma maneira de extrair os registros a serem exibidos na página atual é necessária.

      A segunda é esta linha:

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

      Aqui usa-se o método Array.prototype.slice() para extrair a parte dos registros requisitada de allCountries passando o offset como o índice de partida para a fatia e (offset + pageLimit) como o índice antes do qual a fatia deve terminar.

      Nota: neste tutorial, não foram coletados registros de nenhuma fonte externa. Em um aplicativo real, você provavelmente estaria buscando registros de um banco de dados ou uma API. A lógica para coletar os registros pode entrar no método onPageChanged() do componente App.

      Vamos supor que você tenha um ponto de extremidade de API fictício /api/countries?page={current_page}&limit={page_limit}. O código a seguir mostra como é possível buscar os países sob demanda a partir da API usando o pacote HTTP [axios][“axios]:

      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 });
          });
      }
      

      Agora, finalize o componente App adicionando o método render().

      Na classe App, após o componentDidMount e onPageChanged, adicione o seguinde método render:

      src/App.js

      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>
          );
        }
      }
      

      No método render(), renderiza-se o número total de países, a página atual, o número total de páginas, o controle <Pagination> e o <CountryCard> para cada país na página atual.

      Observe que o método onPageChanged() definido anteriormente na propriedade onPageChanged do controle <Pagination> foi passado. Isso é muito importante para capturar alterações de página a partir do componente Pagination. Além disso, estão sendo exibidos 18 países por página.

      Neste ponto, o aplicativo estará parecido com o mostrado na captura de tela a seguir:

      Captura de tela do aplicativo com 248 países listados e números de página no topo para percorrer cada página

      Agora, você possui um componente App que exibe vários componentes CountryCard e um componente Pagination que divide o conteúdo em páginas separadas. Em seguida, você irá explorar a estilização do seu aplicativo.

      Passo 5 — Adicionando estilos personalizados

      Você pode ter notado que algumas classes personalizadas foram sendo adicionadas aos componentes criados anteriormente. Vamos definir algumas regras de estilo para essas classes no arquivo src/App.scss.

      O arquivo App.scss ficará parecido com o seguinte código:

      src/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;
          }
        }
      }
      

      Modifique seu arquivo App.js para fazer referência ao App.scss ao invés do App.css.

      Nota: para mais informações sobre isso, consulte a documentação do Create React App.

      src/App.js

      import React, { Component } from 'react';
      import Countries from 'countries-api';
      import './App.scss';
      import Pagination from './components/Pagination';
      import CountryCard from './components/CountryCard';
      

      Após adicionar os estilos, o aplicativo terá um visual semelhante ao da captura de tela a seguir:

      Captura de tela do aplicativo, páginas 1 a 14, com estilos

      Agora, você possui um aplicativo completo com uma estilização personalizada adicional. É possível utilizar estilos personalizados para modificar e aprimorar todos os estilos padrão disponibilizados por bibliotecas como o Bootstrap.

      Conclusão

      Neste tutorial, você criou um widget de paginação personalizada em seu aplicativo React. Embora não tenha feito chamadas para nenhuma API, nem interagido com qualquer backend de banco de dados neste tutorial, seu aplicativo pode exigir essas interações. Não sinta-se limitado à abordagem usada neste tutorial de maneira alguma — você pode estendê-la conforme desejar para que atenda aos requisitos do seu aplicativo.

      Para o código fonte completo deste tutorial, confira o repositório build-react-pagination-demo no GitHub. Se quiser, obtenha também uma demonstração em tempo real deste tutorial no Code Sandbox.

      Se quiser aprender mais sobre o React, dê uma olhada em nossa série Como programar no React.js, ou confira nossa página do tópico React para exercícios e projetos de programação.



      Source link