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. devido 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, // Used in JSX as a key
  name: 'Creola Katherine Johnson',
  profession: 'mathematician',
  accomplishment: 'spaceflight calculations',
  imageId: 'MK3eW3A'
}, {
  id: 1, // Used in JSX as a key
  name: 'Mario José Molina-Pasquel Henríquez',
  profession: 'chemist',
  accomplishment: 'discovery of Arctic ozone hole',
  imageId: 'mynHUSa'
}, {
  id: 2, // Used in JSX as a key
  name: 'Mohammad Abdus Salam',
  profession: 'physicist',
  accomplishment: 'electromagnetism theory',
  imageId: 'bE7W1ji'
}, {
  id: 3, // Used in JSX as a 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'
}];

Deep Dive

Displaying several DOM nodes for each list item

¬Ņ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/IDs 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 especifícas un 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 unas IDs basadas 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>
  );
}