Crear repeticiones en templates JSX con React

  • Por
En este artículo vamos a mostrar cómo se deben crear repeticiones, para mostrar listados de ítems, en templates creados con JSX para componentes React.

En este artículo del Manual de React, después de haber abordado las estructuras condicionales en JSX, nos vamos a dedicar a otro importante tipo de estructura que podemos usar a la hora de construir templates: la creación de repeticiones. Este es un recurso que usarás cada vez que tengas que mostrar listados de ítems en una vista, a partir de datos alojados en un array.

La tarea es bastante sencilla y se realiza mayormente usando una herramienta del propio lenguaje Javascript, el método map() disponible en los Arrays. Este método sirve para producir un nuevo array a partir de una repetición por cada uno de los elementos del array inicial.

Un uso de map() lo podemos ver a continuación.

let miArray = [1, 2, 4, 8];
let transformado = miArray.map(item => item * 2);
console.log(transformado);

Este código produce una salida en la consola del array [2, 4, 8, 16].

Supongo que a estas alturas ya estarás familiarizado con las arrow functions, pero por si acaso, este código sería equivalente (lo remarco porque vamos a usar bastantes arrow functions en este artículo).

let transformado = miArray.map(function(item) {
  return item * 2;
});
Nota: si tienes dudas con esto, por favor, lee el artículo sobre las arrow functions de ES6.

Sabiendo usar el método map del array tienes bastante hecho por delante para aprender a producir tus primeras repeticiones en JSX. Sin embargo, hay un detalle que lo complica un poquito más y nos hará alargarnos algo, que es el uso de llaves. Pero vamos poco a poco comenzando por una repetición simple.

Crear una repetición básica en JSX

Comencemos por crear una repetición sencilla, sin preocuparnos todavía de las llaves. Para ello vamos a necesitar un componente que tenga una propiedad o estado con valor de array. En el método render() realizaremos la repetición en ese array, creando un elemento de lista para cada ítem del array.

import React, { Component } from 'react';

class RepeatComponent extends Component {
  constructor(props) {
    super(props);
    this.state = { 
      lenguajes: ['Javascript', 'JSX', 'Typescript', 'NodeJS']
    }
  }
  render() { 
    return (
      <ul>
        {this.state.lenguajes.map(item => <li>{item}</li>)}
      </ul>
    );
  }
}

export default RepeatComponent;

El código de la repetición queda bastante compacto aunque, si no estás acostumbrado a ver este tipo de construcciones, quizás puede chocar algo al principio.

Para entenderlo piensa que el método map está devolviendo un array de "sub-templates". Para cada ítem genera un nuevo sub-template y lo almacena en un array. Luego, el template principal simplemente hace que se renderice ese array de sub-templates.

Este sería un código equivalente, sin la arrow function, por si sirve de ayuda.

render() { 
  return (
    <ul>
      {this.state.lenguajes.map(function(item) {
        return <li>{item}</li>
      })}
    </ul>
  );
}

Incluso hay personas que prefieren componer unos templates a partir de llamadas a otras funciones dentro del componente.

languageItems(items) {
  return items.map((item) => {
    return <li>{item}</li>
  });
} 

render() { 
  return (
    <ul>
      {this.languageItems(this.state.lenguajes)}
    </ul>
  );
}
Nota: Como sabes, a la hora de organizar el código, lo ideal es que lo hagas de la manera que te parezca más claro, para facilitarte el mantenimiento del código más adelante. Personalmente yo prefiero ver la repetición en el mismo código JSX, antes de generar un método extra que me obliga a entender el template en varios pasos. Sin embargo, si lo que vamos a repetir es muy complejo y consta de mucha cantidad de marcado quizás pueda tener sentido usar un método o función extra. No obstante, en este caso quizás te interesa "componetizar" y crear un nuevo componente que encapsule y te abstraiga de todo lo complejo que sea el marcado a repetir. Luego veremos un ejemplo con esto.

Usar llaves (keys) para los ítem de la repetición

El código del componente anterior realiza la repetición en el template correctamente. Al ejecutarse obtendremos un listado de cada uno de sus ítem en la vista, sin embargo, podremos apreciar que aparece un warning en la consola: (Warning: Each child in a list should have a unique "key" prop).

Este mensaje te sale porque en React se deben crear una especie de llaves primarias para cada uno de los ítem de la repetición. Como en el código anterior no los hemos expresado, se nos advierte desde la consola para corregir el problema. Aunque, en realidad no se trata de un problema, en principio, porque React genera unas keys predeterminadas, basadas en el número del índice de cada elemento del array y las usa en caso que no se hayan indicado manualmente.

Sin embargo, las llaves generadas a partir de los índices no son muy seguras. Para comenzar, si ordenamos el array dinámicamente de distintos modos los índices cambiarán y el componente podría comenzar a funcionar de manera errática. Incluso, aún en el caso que no se necesite ordenar el array dinámicamente, podemos encontrarnos ante varias desventajas, como una caída del rendimiento.

Puedes encontrar más información sobre cómo afecta el uso de llaves índice a las repeticiones en la documentación de React: Lists and Keys.

Sea como fuere, lo ideal es siempre indicar nuestros propios índices. Para ello podemos seguir varias estratégias:

Usar los índices de los ítem de la base de datos

Lo más normal es que los datos a listar te lleguen desde una base de datos. En este caso, podrías usar las propias claves primarias de los elementos como índices en el listado.

Por ejemplo, para un array de esta forma:

[
  {
    id: 1,
    title: 'Primer mensaje',
    content: 'Este es el contenido del primer mensaje'
  },
  {
    id: 2,
    title: 'Segundo mensaje',
    content: 'Otro mensaje para listarlo con React'
  },
]

Podríamos hacer una repetición con este código JSX:

<div>
  {this.state.msgs.map((item) => 
    <article key={item.id}>
      <h3>{item.title}</h3>
      <p>{item.content}</p>
    </article>
  )}
</div>

Como puedes ver, en el tag ARTICLE estamos especificando la key, asignando el valor de cada identificador de mensaje. Es tan sencillo como esto.

Qué hacer si no tienes un identificador único

Si no tienes llaves primarias o identificadores en cada uno de tus ítem, podrías usar el propio index del array, que también te entregan como parámetro en la función que envías al método map(). Sería de esta forma.

{this.msgs.map((item, index) =>
  <article key={index}>
    <h3>{item.title}</h3>
    <p>{item.content}</p>
  </article>
);

Sin embargo, aunque esto te ahorre ver el warning en la consola, estaremos ante el mismo problema que no poner nada, pues este índice es el que React crea automáticamente para ti, que tiene los mencionados problemas de rendimiento o hasta funcionamiento.

Componetizar el JSX de cada repetición

"Componetizar" es uno de nuestros verbos preferidos a la hora de desarrollar con arquitectura de componentes. Básicamente nos referimos a que, cuando tenemos algo suficientemente complejo, es mejor separarlo en un componente, para abstraernos de esa complejidad y de paso poder reutilizar el componente más adelante si fuera necesario.

En el caso de la repetición sobre el array de comentarios, el JSX que se va a usar para cada comentario es suficientemente complejo como para que creemos un componente "tonto" (sin estado) que nos aporte una mayor claridad en la repetición.

Este es el código que podríamos usar para crear este componente sin estado:

import React from 'react';

const CommentComponent = (props) => {
  return ( 
    <article>
      <h3>{props.comment.title}</h3>
      <p>{props.comment.content}</p>
    </article>
  );
}

export default CommentComponent;
Nota: en el anterior código estamos usando lo que se conoce como "stateless function component", que ya explicamos en el artículo sobre el estado de los componentes.

Ahora, para hacer la repetición y mostrar todos los comentarios que tenemos en un array, usamos este mismo componente, lo que nos simplificará bastante el código del componente principal.

render() { 
  return (
    <div>
      {this.state.msgs.map((item) => <CommentComponent key={item.id} comment={item} />)}
    </div>
  );
}

Conclusión sobre las repeticiones para listados en JSX

Bien, hemos aprendido algo tan importante en el desarrollo de componentes, como la realización de un listado a partir de los elementos de un array. Para producir esta repetición usamos el método map(), con el que conseguimos generar un array con cada uno de los templates a representar para cada ítem.

La cosa sería tan simple como lo que acabamos de ver, si no fuera porque en React necesitamos generar unas llaves únicas para cada elemento de la repetición. Esas llaves las podemos recibir desde el backend, con lo que usarlas sería algo directo. Sin embargo, si no las tenemos, podemos usar cualquier otro recurso a nuestro alcance.

Las llaves no tienen por qué ser numéricas, simplemente tienen que ser únicas para que los mecanismos internos de React funcionen correctamente,

Autor

Miguel Angel Alvarez

Miguel es fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Comenzó en el mundo del desarrollo web en el año 1997, transformando su hobby en su trabajo.

Compartir