Tutorial

Cómo crear componentes wrapper en React con props

DevelopmentJavaScriptReact

El autor seleccionó Creative Commons para recibir una donación como parte del programa Write for DOnations.

Introducción

En este tutorial, creará componentes wrapper con props usando la biblioteca JavaScript React. Los componentes wrapper envuelven componentes desconocidos y proporcionan una estructura predeterminada para visualizar los componentes secundarios. Este patrón es útil para crear elementos de interfaz usuario (UI) que se utilizan en repetidas ocasiones a lo largo de un diseño, como modales, páginas de plantilla e iconos de información.

Para crear componentes wrapper, primero, aprenderá a usar los operadores rest y spread para recopilar props no utilizadas y pasarlas a componentes anidados. Luego, creará un componente que utiliza el componente children incorporado para envolver componentes anidados en JSX como si fueran elementos HTML. Por último, pasará componentes como props para crear wrappers flexibles que pueden insertar JSX personalizado en varias ubicaciones de un componente.

A lo largo del tutorial, compilará componentes para visualizar una lista de datos de animales en forma de tarjetas. Aprenderá a dividir datos y componentes de refactorización al crear componentes envolventes flexibles. Al final de este tutorial, tendrá una aplicación en funcionamiento que utilizará técnicas avanzadas de props para crear componentes reutilizables que se ampliarán y adaptarán a medida que su aplicación vaya creciendo y cambiando.

Nota: En el primer paso se establece un proyecto en blanco sobre el cual creará el ejercicio de tutorial. Si ya tiene un proyecto de trabajo y desea empezar directamente a trabajar con props, comience con el paso 2.

Requisitos previos

Paso 1: Crear un proyecto vacío

En este paso, creará un nuevo proyecto usando Create React App. Luego, eliminará el proyecto de ejemplo y los archivos relacionados que se instalan al iniciar el proyecto. Por último, creará una estructura de archivos simple para organizar sus componentes. Esto le proporcionará una base sólida para la creación de la aplicación wrapper de este tutorial en el siguiente paso.

Para comenzar, cree un nuevo proyecto. En su línea de comandos, ejecute la siguiente secuencia de comandos para instalar un proyecto nuevo usando create-react-app​​​1​​:

  • npx create-react-app wrapper-tutorial

Al finalizar el proyecto, posiciónese en el directorio:

  • cd wrapper-tutorial

En una nueva pestaña o ventana de terminal, inicie el proyecto usando la secuencia de comandos de inicio de Create React App. El navegador actualizará automáticamente los cambios; por lo tanto, deje esta secuencia de comandos en ejecución mientras trabaje:

  • npm start

Obtendrá un servidor local activo. Si el proyecto no se abrió en una ventana de navegador, puede abrirlo con http://localhost:3000/. Si lo ejecuta desde un servidor remoto, la dirección será http://your_domain:3000.

Su navegador se cargará con una aplicación de React sencilla incluida como parte de la aplicación de Create React App:

Proyecto de plantilla de React

Creará un conjunto completamente nuevo de componentes personalizados, por lo que deberá comenzar eliminando un código de texto estándar para poder tener un proyecto vacío.

Empiece abriendo src/App.js en un editor de texto. Es el componente root que se inserta en la página. Todos los componentes empezarán desde aquí. Puede obtener más información sobre App.js en Cómo configurar un proyecto de React con Create React App.

Abra src/App.js con el siguiente comando:

  • nano src/App.js

Verá un archivo como este:

wrapper-tutorial/src/App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

Elimine la línea import logo from './logo.svg';​​​. Luego, sustituya todo en instrucción return para mostrar un conjunto de etiquetas vacías: <</>. Esto le dará como resultado una página de válida que no muestra nada. El código final tendrá el siguiente aspecto:

wrapper-tutorial/src/App.js

import React from 'react';
import './App.css';

function App() {
  return <></>;
}

export default App;

Guárdelo y salga del editor de texto.

Por último, elimine el logo. No lo usará en su aplicación y debería eliminar los archivos que no se usen a medida que el trabajo avance. Le evitará confusiones a la larga.

En la ventana de la terminal, escriba el siguiente comando:

  • rm src/logo.svg

Si observa su navegador, verá una pantalla en blanco.

Pantalla en blanco en Chrome

Ahora que limpió el proyecto Create React App, cree una estructura de archivos sencilla. Esto le permitirá mantener sus componentes aislados y preservar su independencia.

Cree un directorio llamado components en el directorio src. Contendrá todos sus componentes personalizados.

  • mkdir src/components

Cada componente tendrá su propio directorio para almacenar el archivo de componentes junto con los estilos, las imágenes, si hay alguna, y las pruebas.

Cree un directorio para App:

  • mkdir src/components/App

Mueva todos los archivos de la App a ese directorio. Utilice el comodín, *, para seleccionar cualquier archivo que empiece con App. sin importar la extensión. Luego, utilice el comando mv para disponerlos en el directorio nuevo:

  • mv src/App.* src/components/App

Por último, actualice la ruta de importación relativa en index.js, el componente root que inicia todo el proceso:

  • nano src/index.js

La instrucción import debe apuntar al archivo App.js en el directorio App; realice el siguiente cambio resaltado:

wrapper-tutorial/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Guarde el archivo y ciérrelo.

Ahora que el proyecto está configurado, puede crear su primer componente.

Paso 2: Recopilar props no utilizadas con ...props

En este paso, creará un componente para mostrar un conjunto de datos sobre un grupo de animales. Su componente contendrá un segundo componente anidado para mostrar cierta información visualmente. Para conectar el componente principal y el anidado, utilizará los operadores rest y spread para pasar las props no utilizadas del componente principal al secundario sin necesidad de que el principal tenga conocimiento del nombre o el tipo de las props.

Al final de este paso, tendrá un componente principal que puede proporcionar props a componentes anidados sin necesidad de saber cuáles son esas props. Esto mantendrá el componente principal flexible, lo que le permitirá actualizar el componente secundario sin tener que modificar el principal.

Crear un componente AnimalCard

Para comenzar, cree un conjunto de datos de los animales. Primero, abra un archivo que contenga el conjunto de datos en el directorio components/App:

  • nano src/components/App/data.js

Añada los siguientes datos:

src/components/App/data.js

export default [
  {
    name: 'Lion',
    scientificName: 'Panthero leo',
    size: 140,
    diet: ['meat']
  },
  {
    name: 'Gorilla',
    scientificName: 'Gorilla beringei',
    size: 205,
    diet: ['plants', 'insects']
  },
  {
    name: 'Zebra',
    scientificName: 'Equus quagga',
    size: 322,
    diet: ['plants'],
  }
]

Esta lista de animales es una matriz de objetos que incluye el nombre del animal, el nombre científico, el peso y la alimentación.

Guarde y cierre el archivo.

A continuación, cree un directorio para el componente AnimalCard:

  • mkdir src/components/AnimalCard

Abra un nuevo archivo en el directorio:

  • nano src/components/AnimalCard/AnimalCard.js

Ahora, agregue un componente que tomará name, diet y size como props y mostrará lo siguiente:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';

export default function AnimalCard({ diet, name, size }) {
  return(
    <div>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <div>{diet.join(', ')}.</div>
    </div>
  )
}

AnimalCard.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

Aquí, está desestructurando las props de la lista de parámetros para la función AnimalCard y, luego, mostrando los datos en un div. Los datos de diet se enumeran como una sola cadena usando el método join(). Cada porción de datos incluye un PropType correspondiente para garantizar que el tipo de datos sea correcto.

Guarde y cierre el archivo.

Ahora que tiene su componente y sus datos, deberá combinarlos. Para hacerlo, importe el componente y los datos al componente root de su proyecto: App.js.

Primero, abra el componente:

  • nano src/components/App/App.js

Desde allí, puede ejecutar un bucle en los datos y devolver un nuevo AnimalCard con las props relevantes. Añada las líneas resaltadas a App.js:

wrapper-tutorial/src/components/App/App.js
import React from 'react';
import './App.css';

import animals from './data';
import AnimalCard from '../AnimalCard/AnimalCard';

function App() {
  return (
    <div className="wrapper">
      {animals.map(animal =>
        <AnimalCard
          diet={animal.diet}
          key={animal.name}
          name={animal.name}
          size={animal.size}
        />
      )}
    </div>
  );
}

export default App;

Guarde y cierre el archivo.

A medida que trabaje en proyectos más complejos, sus datos provendrán de ubicaciones más variadas, como API, localStorage o archivos estáticos. Pero el proceso para usar cada una de ellas será similar: deberá asignar los datos a una variable y ejecutar un bucle en ellos. En este caso, los datos provienen de un archivo estático, por lo tanto, está importando directamente a una variable.

En este código, utiliza el método .map() para iterar en animals y mostrar las props. Observe que no es necesario usar todos los datos; no está pasando explícitamente la propiedad scientificName, por ejemplo. También está añadiendo una prop key independiente que React utilizará para realizar un seguimiento de los datos asignados. Por último, envuelve el código en un div con un className de wrapper que usará para añadir algo de estilo.

Para añadir este estilo, abra App.css:

  • nano src/components/App/App.css

Elimine el estilo estándar y añada propiedades flex a una clase denominada wrapper:

prop-tutorial/src/components/App/App.js
.wrapper {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    padding: 20px;
}

Esto utilizará la disposición de flexbox para organizar los datos de modo que se alineen. padding da algo de espacio en la ventana del navegador, y justify-content extiende el espacio adicional entre los elementos.

Guarde el archivo y ciérrelo. Cuando lo haga, el navegador se actualizará y verá algunos datos espaciados.

Navegador con datos espaciados

Crear un componente de detalles

Ahora, tiene un componente sencillo que muestra los datos. Pero supongamos que quiere darle un toque especial a los datos de diet al convertir el texto en un emoji. Puede hacerlo convirtiendo los datos de su componente.

React está diseñado para ser flexible; por lo tanto, para convertir datos, tiene distintas opciones:

  • Puede crear una función dentro del componente que convierta el texto en un emoji.
  • Puede crear una función y almacenarla en un archivo fuera del componente para poder volver a utilizar la lógica en distintos componentes.
  • Puede crear un componente separado que convierta el texto en un emoji.

Se puede usar cualquiera de los métodos siempre y cuando se aplique al caso de uso correcto, y verá que los irá alternando al crear una aplicación. Para evitar la abstracción y la complejidad prematuras, debería utilizar la primera opción para empezar.  Si desea volver a utilizar la lógica, puede extraer la función del componente de forma separada.  La tercera opción es la mejor si desea tener una porción reutilizable que incluya la lógica y el marcado o que quiera aislar para usar en toda la aplicación.

En este caso, crearemos un componente nuevo, dado que queremos añadir más datos más adelante y estamos combinando marcado con lógica de conversión.

El componente nuevo se llamará AnimalDetails. Para hacerlo, cree un directorio nuevo:

  • mkdir src/components/AnimalDetails

A continuación, abra AnimalDetails.js en su editor de texto:

  • nano src/components/AnimalDetails/AnimalDetails.js

Dentro del archivo, cree un componente pequeño que muestre diet como un emoji:

wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.js
import React from 'react';
import PropTypes from 'prop-types';
import './AnimalDetails.css';

function convertFood(food) {
  switch(food) {
    case 'insects':
      return '🐜';
    case 'meat':
      return '🍖';
    case 'plants':
    default:
      return '🌱';
  }
}

export default function AnimalDetails({ diet }) {
  return(
    <div className="details">
      <h4>Details:</h4>
      <div>
        Diet: {diet.map(food => convertFood(food)).join(' ')}
      </div>
    </div>
  )
}

AnimalDetails.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
}

El objeto AnimalDetails.propTypes configura la función para que tome una prop de diet que es una matriz de cadenas. Luego, dentro del componente, el código ejecuta un bucle en diet y convierte la cadena en un emoji utilizando la instrucción switch.

Guarde y cierre el archivo.

También está importando algo de CSS, por lo tanto, vamos a añadirlo ahora.

Abra AnimalDetails.css:

  • nano src/components/AnimalDetails/AnimalDetails.css

Añada algo de CSS para darle al elemento un borde y un margen para separar los detalles del resto del componente:

wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.css
.details {
    border-top: gray solid 1px;
    margin: 20px 0;
}

Usamos .details para la regla se aplique a los elementos con un className de details.

Guarde y cierre el archivo.

Ahora que tiene un componente personalizado nuevo, puede añadirlo a su componente AnimalCard. Abra AnimalCard.js:

  • nano src/components/AnimalCard/AnimalCard.js

Sustituya la instrucción diet.join por el componente nuevo AnimalDetails y pase diet como prop agregando las líneas resaltadas:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';
import AnimalDetails from '../AnimalDetails/AnimalDetails';

export default function AnimalCard({ diet, name, size }) {
  return(
    <div>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        diet={diet}
      />
    </div>
  )
}

AnimalCard.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

Guarde el archivo y verá los detalles nuevos en el navegador.

Navegador con detalles

Pasar detalles a través de un componente con ...props

Los componentes están funcionando bien en conjunto, pero hay una ligera ineficiencia en AnimalCard. Está extrayendo diet de forma explícita del argumento props, pero no está usando los datos; en su lugar, los está pasando al componente.  No hay nada intrínsecamente malo en ello; de hecho, suele ser mejor pecar por exceso de comunicación. Pero al hacerlo, hace que su código sea más difícil de mantener. Cuando quiera pasar datos nuevos a AnimalDetails, deberá actualizar tres ubicaciones: App, donde pasa las props, AnimalDetails, que consume la prop, y AnimalCard, que es el intermediario.

Una mejor opción es reunir todas las props sin usar dentro de AnimalCard y, luego, pasarlas directamente a AnimalDetails. Esto le permite realizar cambios en AnimalDetails sin cambiar AnimalCard. De hecho, AnimalCard no necesita saber nada sobre las props o los PropTypes que se pasan a AnimalDetails.

Para hacerlo, usará el operador de objeto rest. Este operador recopila cualquier elemento que no se extraiga durante la desestructuración y lo guarda en un objeto nuevo.

Este es un ejemplo sencillo:

const dog = {
    name: 'dog',
    diet: ['meat']
}

const { name, ...props  } = dog;

En este caso, la variable name será 'dog' y las props de la variable serán { diet: ['meat']}

Hasta ahora, ha pasado todas las props como si fueran atributos HTML, pero también puede usar objetos para enviar props. Para usar un objeto como prop, deberá utilizar el operador de propagación ...props entre llaves. Esto convertirá cada par clave-valor en una prop.

Abra AnimalCard.js:

  • nano src/components/AnimalCard/AnimalCard.js

En su interior, elimine diet del objeto desestructurado y, en su lugar, recopile el resto de las props en una variable denominada props. Luego, pase esas props directamente a AnimalDetails:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';
import AnimalDetails from '../AnimalDetails/AnimalDetails';

export default function AnimalCard({ name, size, ...props }) {
  return(
    <div>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        {...props}
      />
    </div>
  )
}

AnimalCard.propTypes = {
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

Observe que puede eliminar el PropType diet, ya que no está usando la prop en este componente.

En este caso, está pasando una sola prop a AnimalDetails. En casos en los que tenga varias props, el orden se tendrá en cuenta. Una prop posterior sobrescribirá las props anteriores; por lo tanto, si desea priorizar una prop, asegúrese de que sea la última.  Esto puede causar confusión si su objeto props tiene una propiedad que también es un valor con nombre.

Guarde y cierre el archivo. El navegador se actualizará, y todo se verá de la misma manera:

Navegador con detalles

Para ver cómo el objeto ...props añade flexibilidad, vamos a pasar scientificName a AnimalDetails a través del componente AnimalCard.

Primero, abra App.js:

  • nano src/components/App/App.js

Luego, pase scientificName como prop:

wrapper-tutorial/src/components/App/App.js
import React from 'react';
import './App.css';

import animals from './data';
import AnimalCard from '../AnimalCard/AnimalCard';

function App() {
  return (
    <div className="wrapper">
      {animals.map(animal =>
        <AnimalCard
          diet={animal.diet}
          key={animal.name}
          name={animal.name}
          size={animal.size}
          scientificName={animal.scientificName}
        />
      )}
    </div>
  );
}

export default App;

Guarde y cierre el archivo.

Omita AnimalCard, dado que no necesitará hacer ningún cambio allí. Luego, abra AnimalDetails para poder consumir la prop nueva:

  • nano src/components/AnimalDetails/AnimalDetails.js

La prop nueva será una cadena, que añadirá a la lista details junto con una línea que declara el PropType:

wrapper-tutorial/src/components/AnimalDetails/AnimalDetails.js
import React from 'react';
...
export default function AnimalDetails({ diet, scientificName }) {
  return(
    <div className="details">
      <h4>Details:</h4>
      <div>
        Scientific Name: {scientificName}.
      </div>
      <div>
        Diet: {diet.map(food => convertFood(food)).join(' ')}
      </div>
    </div>
  )
}

AnimalDetails.propTypes = {
  diet: PropTypes.arrayOf(PropTypes.string).isRequired,
  scientificName: PropTypes.string.isRequired,
}

Guarde y cierre el archivo. Cuando lo haga, el navegador se actualizará y verá los detalles nuevos sin ningún cambio en el componente AnimalCard:

Navegador con nombre científico

En este paso, aprendió a crear props primarias flexibles que pueden tomar props desconocidas y pasarlas a componentes anidados con el operador de propagación.  Este es un patrón común que le proporcionará la flexibilidad necesaria para crear componentes con responsabilidades definidas. En el siguiente paso, creará componentes que pueden tomar componentes desconocidos como props usando la prop incorporada children.

Paso 3: Crear componentes wrapper con children

En este paso, creará un componente wrapper que puede tomar un grupo desconocido de componentes como prop. Esto le dará la capacidad de anidar componentes como HTML estándar y le proporcionará un patrón para crear wrappers reutilizables que le permitirán crear una variedad de componentes que necesitan un diseño común pero un interior flexible.

React le proporciona una prop integrada children que recopila todos los componentes secundarios. Al utilizarla, la creación de componentes wrapper se hace más intuitiva y legible.

Para comenzar, cree un componente nuevo denominado Card. Será un componente wrapper para crear un estilo estándar para cualquier componente de tarjetas nuevo.

Cree un directorio nuevo:

  • mkdir src/components/Card

Luego, abra el componente Card en su editor de texto:

  • nano src/components/Card/Card.js

Cree un componente que tome a children y title como props y las envuelva en un div añadiendo el siguiente código:

wrapper-tutorial/src/components/Card/Card.js
import React from 'react';
import PropTypes from 'prop-types';
import './Card.css';

export default function Card({ children, title }) {
  return(
    <div className="card">
      <div className="card-details">
        <h2>{title}</h2>
      </div>
      {children}
    </div>
  )
}

Card.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.element),
    PropTypes.element.isRequired
  ]),
  title: PropTypes.string.isRequired,
}

Los PropTypes para children son nuevos. La prop children puede ser un elemento o una matriz de elementos JSX. La prop title es una cadena.

Guarde y cierre el archivo.

A continuación, agregue algo de estilo. Abra Card.css:

  • nano src/components/Card/Card.css

Su tarjeta tendrá un borde y una línea debajo de los detalles.

wrapper-tutorial/src/components/Card/Card.css
.card {
    border: black solid 1px;
    margin: 10px;
    padding: 10px;
    width: 200px;
}

.card-details {
    border-bottom: gray solid 1px;
    margin-bottom: 20px;
}

Guarde y cierre el archivo. Ahora que tiene su componente, debe usarlo. Podría envolver cada AnimalCard con el componente Card en App.js, pero como el nombre AnimalCard ya implica que es Card, sería mejor usar el componente Card dentro de AnimalCard.

Abra AnimalCard:

  • nano src/components/AnimalCard/AnimalCard.js

A diferencia de otras props, no pasa children de forma explícita. En su lugar, incluye los JSX como si fueran elementos HTML secundarios. En otras palabras, solo debe anidarlos dentro del elemento, como se indica a continuación:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
import React from 'react';
import PropTypes from 'prop-types';
import Card from '../Card/Card';
import AnimalDetails from '../AnimalDetails/AnimalDetails';

export default function AnimalCard({ name, size, ...props }) {
  return(
    <Card title="Animal">
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        {...props}
      />
    </Card>
  )
}

AnimalCard.propTypes = {
  name: PropTypes.string.isRequired,
  size: PropTypes.number.isRequired,
}

A diferencia de los componentes de React, no es necesario tener un solo elemento root como secundario. Es por eso que el PropType de Card especificó que se puede tener un una matriz de elementos o uno solo. Además de pasar children como componentes anidados, le está dando a la tarjeta el título Animal.

Guarde y cierre el archivo. Al hacerlo, el navegador se actualizará y verá el componente de la tarjeta actualizado.

Navegador con tarjetas

Ahora, tiene un componente Card reutilizable que puede admitir cualquier cantidad de componentes secundarios anidados.  La principal ventaja de esto es que puede volver a utilizar Card con cualquier componente. Si quisiera crear una tarjeta Plant, podría hacerlo envolviendo la información de la planta con el componente Card.  Y lo puede usar con componentes que no estén relacionados en absoluto: si quisiera reutilizar el componente Card en una aplicación completamente distinta que enumere cosas como música o datos de una cuenta, también podría hacerlo.  Al componente Card le es indistinto qué son los componentes secundarios; solo está reutilizando el elemento wrapper, que, en este caso, representa el título y el borde con estilo.

La desventaja de usar children es que solo puede tener una instancia de la prop secundaria. A veces, querrá que un componente tenga JSX personalizado en varias ubicaciones. Afortunadamente, puede hacerlo pasando JSX y componentes de React como props, lo que veremos en el siguiente paso.

Paso 4: Pasar componentes como props

En este paso, modificará su componente Card para que admita otros componentes como props. Esto le dará a su componente máxima flexibilidad para mostrar componentes desconocidos o JSX en varias ubicaciones a lo largo de la página. A diferencia de children, que se puede usar una sola vez, puede tener todos los componentes que desee como props, lo que le brinda al componente wrapper la capacidad de adaptarse a una variedad de necesidades sin dejar de mantener un aspecto y una estructura estándar.

Al final de este paso, tendrá un componente que puede envolver componentes de secundarios y, también, mostrar otros componentes en la tarjeta. Este patrón le brindará flexibilidad cuando necesite crear componentes que requieran información más compleja que cadenas y enteros simples.

Vamos a modificar el componente Card para que tome un elemento de React arbitrario denominado details.

Primero, abra el componente Card:

  • nano src/components/Card/Card.js

A continuación, agregue una prop nueva denominada details y colóquela debajo del elemento <h2>:

wrapper-tutorial/src/components/Card/Card.js
import React from 'react';
import PropTypes from 'prop-types';
import './Card.css';

export default function Card({ children, details, title }) {
  return(
    <div className="card">
      <div className="card-details">
        <h2>{title}</h2>
        {details}
      </div>
      {children}
    </div>
  )
}

Card.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.element),
    PropTypes.element.isRequired
  ]),
  details: PropTypes.element,
  title: PropTypes.string.isRequired,
}

Card.defaultProps = {
  details: null,
}

Esta prop será del mismo tipo que children, pero deberá ser opcional. Para hacerla opcional, añada el valor predeterminado null. En este caso, si un usuario no pasa detalles, el componente seguirá siendo válido y no mostrará nada adicional.

Guarde y cierre el archivo. La página se actualizará, y verá la misma imagen que antes:

Navegador con tarjetas

Ahora, agregue algunos detalles a AnimalCard. Primero, abra AnimalCard.

  • nano src/components/AnimalCard/AnimalCard.js

Como el componente Card ya está usando children, deberá pasar el componente JSX nuevo como prop. Dado que todos los animales son mamíferos, agregue este dato a la tarjeta, pero envuélvalo en etiquetas <em> para que quede en cursiva.

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
import React from 'react';
...

export default function AnimalCard({ name, size, ...props }) {
  return(
    <Card title="Animal" details={<em>Mammal</em>}>
      <h3>{name}</h3>
      <div>{size}kg</div>
      <AnimalDetails
        {...props}
      />
    </Card>
  )
}
...

Guarde el archivo. Al hacerlo, el navegador se actualizará y verá la actualización, incluida la frase Mammal.

Navegador con tarjetas y detalles

Esta prop ya es eficaz porque puede admitir JSX de cualquier tamaño. En este ejemplo, solo añadió un elemento, pero podría pasar todos los JSX que quiera. Y no es necesario que sean JSX. Si tiene un marcado complicada, por ejemplo, no sería conveniente pasarlo directamente a la prop, dado que sería difícil de leer. En su lugar, podría crear un componente separado y, luego, pasarlo como prop.

Para ver cómo funciona este proceso, pase AnimalDetails a la prop details:

wrapper-tutorial/src/components/AnimalCard/AnimalCard.js
import React from 'react';
...

export default function AnimalCard({ name, size, ...props }) {
  return(
    <Card
      title="Animal"
      details={
        <AnimalDetails
          {...props}
        />
      }
    >
      <h3>{name}</h3>
      <div>{size}kg</div>
    </Card>
  )
}
...

AnimalDetails es más complejo y tiene varias líneas de marcado. Si lo añadiera directamente a details, aumentaría la prop considerablemente y dificultaría su lectura.

Guarde y cierre el archivo. Al hacerlo, el navegador se actualizará, y los detalles se mostrarán en la parte superior de la tarjeta.

Tarjeta con detalles en la parte superior

Ahora, tiene un componente Card que puede tomar JSX personalizado y colocarlo en varios puntos. No está restringido a usar una sola prop; puede pasar elementos a tantas props como desee. Esto le brinda la posibilidad de crear componentes envolventes flexibles que pueden darles a otros desarrolladores la posibilidad de personalizarlos sin dejar de mantener su estilo y funcionalidad general.

El proceso de pasar componentes como props no es perfecto. Resulta un poco más difícil de leer y no es tan claro como pasar children, pero son igual de flexibles y puede usar tantas como quiera en un componente. Debería usar children primero, pero no dude en volver a recurrir a las props si no es suficiente.

En este paso, aprendió a pasar componentes de React y JSX como props a otro componente. Esto le proporcionará flexibilidad a su componente para manejar muchas situaciones en las que un componente wrapper puede necesitar varias props para administrar JSX o componentes.

Conclusión

Creó varios componentes envolventes que pueden mostrar datos de forma flexible, sin dejar de mantener un aspecto y una estructura predecibles. Creó componentes que pueden recopilar y pasar props desconocidas a componentes anidados. También utilizó la prop children integrada para crear componentes wrapper que pueden manejar cualquier cantidad de elementos anidados. Por último, creó un componente que puede tomar componentes de React o JSX como props para que su componente wrapper pueda administrar varias instancias de diferentes personalizaciones.

Los componentes wrapper le permiten adaptarse a circunstancias desconocidas, así como maximizar la reutilización y la consistencia del código. Este patrón es útil para crear elementos de IU básicos que reutilizará en una aplicación, como botones, alertas, modales y presentaciones, entre otros. Volverá a recurrir a él varias veces.

Si desea acceder a más tutoriales de React, consulte nuestra página temática de React o regrese a la página de la serie Cómo programar con React.js.

Creative Commons License