Renderizado de listas

A menudo querrás mostrar muchos componentes similares de una colección de datos. Puedes usar los métodos de array de JavaScript para manipular un array de datos. En esta página, usarás filter() y map() con React para filtrar y transformar tu array de datos en un array de componentes.

Aprenderás

  • Cómo renderizar componentes desde un array usando el método map() de JavaScript
  • Cómo renderizar solo un componente específico usando filter() de JavaScript
  • Cuándo y cómo usar las keys de React

Renderizar datos desde arrays

Digamos que tienes una lista de contenido.

<ul>
<li>Creola Katherine Johnson: mathematician</li>
<li>Mario José Molina-Pasquel Henríquez: chemist</li>
<li>Mohammad Abdus Salam: physicist</li>
<li>Percy Lavon Julian: chemist</li>
<li>Subrahmanyan Chandrasekhar: astrophysicist</li>
</ul>

La única diferencia entre esos elementos de la lista es su contenido, sus datos. A menudo necesitarás mostrar muchas instancias del mismo componente usando diferentes datos cuando construyas interfaces: desde listas de comentarios a galerías de fotos de perfiles. En estas situaciones, puedes guardar estos datos en objetos de JavaScript y arrays, y usar métodos como map() y filter() para renderizar listas de componentes desde ellos.

Aquí hay un corto ejemplo de como generar una lista de elementos de un array:

  1. Mueve los datos en un array:
const people = [
'Creola Katherine Johnson: mathematician',
'Mario José Molina-Pasquel Henríquez: chemist',
'Mohammad Abdus Salam: physicist',
'Percy Lavon Julian: chemist',
'Subrahmanyan Chandrasekhar: astrophysicist'
];
  1. Mapea los miembros de people en un nuevo array de nodos JSX, listItems:
const listItems = people.map(person => <li>{person}</li>);
  1. Devuelve listItems desde tu componente envuelto en un <ul>:
return <ul>{listItems}</ul>;

Aquí está el resultado:

const people = [
  'Creola Katherine Johnson: mathematician',
  'Mario José Molina-Pasquel Henríquez: chemist',
  'Mohammad Abdus Salam: physicist',
  'Percy Lavon Julian: chemist',
  'Subrahmanyan Chandrasekhar: astrophysicist'
];

export default function List() {
  const listItems = people.map(person =>
    <li>{person}</li>
  );
  return <ul>{listItems}</ul>;
}

Date cuenta que el sandbox anterior muestra un error por consola:

Console
Warning: Each child in a list should have a unique «key» prop.

Aprenderás como arreglar este error más adelante en esta página. Antes de que lleguemos a eso, vamos a añadir algo de estructura a tus datos.

Filtrar arrays de objetos

Estos datos pueden ser estructurados incluso más.

const people = [{
id: 0,
name: 'Creola Katherine Johnson',
profession: 'mathematician',
}, {
id: 1,
name: 'Mario José Molina-Pasquel Henríquez',
profession: 'chemist',
}, {
id: 2,
name: 'Mohammad Abdus Salam',
profession: 'physicist',
}, {
name: 'Percy Lavon Julian',
profession: 'chemist',
}, {
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrophysicist',
}];

Digamos que quieres una manera de mostrar solo las personas cuya profesión sea 'chemist'. Puedes usar el método filter() de JavaScript para devolver solo esas personas. Este método coge un array de objetos, los pasa por un «test» (una función que devuelve true o false), y devuelve un nuevo array de solo esos objetos que han pasado el test (que han devuelto true).

Tú solo quieres los objetos donde profession es 'chemist'. La función «test» para esto se ve como (person) => person.profession === 'chemist'. Aquí está cómo juntarlo:

  1. Crea un nuevo array solo de personas que sean «químicos», chemists, llamando al método filter() en people filtrando por person.profession === 'chemist':
const chemists = people.filter(person =>
person.profession === 'chemist'
);
  1. Ahora mapea sobre chemists:
const listItems = chemists.map(person =>
<li>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
known for {person.accomplishment}
</p>
</li>
);
  1. Por último, devuelve el listItems de tu componente:
return <ul>{listItems}</ul>;
import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const chemists = people.filter(person =>
    person.profession === 'chemist'
  );
  const listItems = chemists.map(person =>
    <li>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        known for {person.accomplishment}
      </p>
    </li>
  );
  return <ul>{listItems}</ul>;
}

Atención

Las funciones de flecha implícitamente devuelven la expresión justo después del =>, así que no necesitas declarar un return:

const listItems = chemists.map(person =>
<li>...</li> // Implicit return!
);

Sin embargo, ¡debes escibir el return explícitamente si tu => está seguida por una llave{!

const listItems = chemists.map(person => { // Curly brace
return <li>...</li>;
});

Las funciones de flecha que tienen => { se dice que tienen un «cuerpo de bloque». Te permiten escribir más de una sola línea de código, pero tienes que declarar un return por ti mismo. Si lo olvidas, ¡Nada será devuelto!

Mantener los elementos de una lista en orden con key

Fíjate que todos los sandboxes anteriores mostraban un error en la consola:

Console
Warning: Each child in a list should have a unique «key» prop.

Tienes que darle a cada elemento del array una key (una cadena de texto o un número) que lo identifique de manera única entre otros elementos del array:

<li key={person.id}>...</li>

Nota

¡Los elementos JSX directamente dentro de una llamada a un map() siempre necesitan keys!

Las keys le indican a React que objeto del array corresponde a cada componente, para así poder emparejarlo más tarde. Esto se vuelve más importante si los objetos de tus arrays se pueden mover (p. ej. debido a un ordenamiento), insertar, o eliminar. Una key bien escogida ayuda a React a entender lo que ha sucedido exactamente, y hacer las correctas actualizaciones en el árbol del DOM.

En vez de generar keys sobre la marcha, deberías incluirlas en tus datos:

export const people = [{
  id: 0, // Usado en JSX como key
  name: 'Creola Katherine Johnson',
  profession: 'mathematician',
  accomplishment: 'spaceflight calculations',
  imageId: 'MK3eW3A'
}, {
  id: 1, // Usado en JSX como key
  name: 'Mario José Molina-Pasquel Henríquez',
  profession: 'chemist',
  accomplishment: 'discovery of Arctic ozone hole',
  imageId: 'mynHUSa'
}, {
  id: 2, // Usado en JSX como key
  name: 'Mohammad Abdus Salam',
  profession: 'physicist',
  accomplishment: 'electromagnetism theory',
  imageId: 'bE7W1ji'
}, {
  id: 3, // Usado en JSX como key
  name: 'Percy Lavon Julian',
  profession: 'chemist',
  accomplishment: 'pioneering cortisone drugs, steroids and birth control pills',
  imageId: 'IOjWm71'
}, {
  id: 4, // Used in JSX as a key
  name: 'Subrahmanyan Chandrasekhar',
  profession: 'astrophysicist',
  accomplishment: 'white dwarf star mass calculations',
  imageId: 'lrWQx8l'
}];

Profundizar

Mostrar varios nodos DOM para cada elemento de una lista

¿Qué haces cuándo cada objeto necesita renderizar no uno, sino varios nodos del DOM?

El atajo de sintaxis del <>...</> Fragment no te dejará pasarle una key, así que necesitas agruparlos en un solo <div>, o usar una sintaxis algo más larga y más explícita del <Fragment>:

import { Fragment } from 'react';

// ...

const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);

Los Fragments desaparecen del DOM, así que esto producirá una lista plana de <h1>, <p>, <h1>, <p>, y así.

Dónde conseguir tu key

Distintas fuentes de datos dan diferentes fuentes de keys:

  • Datos de una base de datos: Si tus datos vienen de una base de datos, puedes usar las keys/ID de la base de datos, que son únicas por naturaleza.
  • Datos generados localmente: Si tus datos son generados y persistidos localmente (p. ej. notas en una app de tomar notas), usa un contador incremental, crypto.randomUUID() o un paquete como uuid cuando este creando objetos.

Reglas de las keys

  • Las keys tienen que ser únicas entre elementos hermanos. Sin embargo, está bien usar las mismas keys para nodos JSX en arrays diferentes.
  • Las keys no tienen que cambiar o ¡eso quitará su propósito! No las generes mientras renderizas.

¿Por qué React necesita keys?

Imagina que los archivos de tu escritorio no tuvieran nombres. En vez de eso, tu te referirías a ellos por su orden — el primer archivo, el segundo, y así. Podrías acostumbrarte a ello, pero una vez borres un archivo, se volvería algo confuso. El segundo archivo se convertiría en el primero, el tercer archivo se convertiría en el segundo, y así.

Los nombres de archivos en una carpeta y las keys JSX en un array tienen un propósito similar. Nos permiten identificar un objeto de manera única entre sus hermanos. Una key bien escogida da más información aparte de la posición en el array. incluso si la posición cambia devido a un reordenamiento, la key permite a React identificar al elemento a lo largo de su ciclo de vida.

Atención

Podrías estar tentado a usar el índice del elemento en el array como su key. De hecho, eso es lo que React usará si tu no especificas una key en absoluto. Pero el orden en el que renderizas elementos cambiará con el tiempo si un elemento es insertado, borrado, o si se reordena su array. El índice como key lleva a menudo a sutiles y confusos errores.

Igualmente, no generes keys sobre la marcha, p. ej. con key={Math.random()}. Esto hará que las keys nunca coincidan entre renderizados, llevando a todos tus componentes y al DOM a recrearse cada vez. No solo es una manera lenta, si no que también pierde cualquier input del usuario dentro de los elementos listados. En vez de eso, usa unos ID basados en datos.

Date cuenta de que tus componentes no reciben la key como un prop. Solo es usado como pista para React. Si tus componentes necesitan un ID, se lo tienes que pasar como una prop separada: <Profile key={id} userId={id} />.

Recapitulación

En esta página has aprendido:

  • Como mover datos fuera de componentes y en estructuras de datos como arrays y objetos.
  • Como genrerar sets de componentes similares con el método map() de JavaScript.
  • Como crear arrays de objetos filtrados con el método filter() de JavaScript.
  • Por qué y cómo poner la key en cada componente en una colección para que React pueda seguir la pista de cada uno de ellos incluso si su posición o datos cambia.

Desafío 1 de 4:
Dividir una lista en dos

Este ejemplo muestra una lista de todas las personas.

Cambiala para mostrar dos listas separadas, una detrás de otra : Químicos y Cualquier otro. Como antes, puedes saber que persona es química comprobando si person.profession === 'chemist'.

import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const listItems = people.map(person =>
    <li key={person.id}>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        known for {person.accomplishment}
      </p>
    </li>
  );
  return (
    <article>
      <h1>Scientists</h1>
      <ul>{listItems}</ul>
    </article>
  );
}