Renderizando Listas

Você frequentemente gostará de exibir múltiplos componentes similares a partir de uma coleção de dados. Você pode usar os métodos de array JavaScript para manipular um array de dados. Nesta página, você usará filter() e map() com o React para filtrar e transformar seu array de dados em um array de componentes.

Você aprenderá

  • Como renderizar componentes a partir de um array usando map() do JavaScript
  • Como renderizar apenas componentes específicos usando filter() do JavaScript
  • Quando e porque usar as keys do React

Renderizando dados de arrays

Digamos que você tenha uma lista de conteúdo.

<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>

As únicas diferenças entre os itens da lista são seus conteúdos, seus dados. Você comumente precisará exibir diversas instâncias do mesmo componente usando diferentes dados ao construir interfaces: de listas de comentários a galeiras de fotos de perfil. Nessas situações, você pode armazenar esses dados em objetos ou arrays JavaScript e usar métodos como map() e filter() para renderizar listas de componentes a partir deles.

Aqui está um breve exemplo de como gerar uma lista de itens a partir de um array:

  1. Mova os dados para um 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. Mapeie os membros de people a um novo array de nós JSX, listItems:
const listItems = people.map(person => <li>{person}</li>);
  1. Retorne listItems do seu componente dentro de uma <ul>:
return <ul>{listItems}</ul>;

Aqui está o 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>;
}

Perceba que a sandbox acima exibe um erro no console:

Console
Warning: Each child in a list should have a unique “key” prop.

Você aprenderá como consertar este erro mais tarde nessa página. Antes de chegarmos nisto, vamos adicionar alguma estrutura aos seus dados.

Filtrando arrays de itens

Esses dados podem ser estruturados ainda mais.

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 você queira uma maneira de mostrar apenas as pessoas cuja profissão é 'chemist'. Você pode usar o método filter() do JavaScript para retornar apenas essas pessoas. Esse método recebe um array de itens, passa-os por um “teste” (uma função que retorna true ou false), e retorna um novo array contendo apenas aqueles itens os quais passaram no teste (retornaram true).

Você quer apenas os itens onde profession seja 'chemist'. A função “teste” para isso se parece com (person) => person.profession === 'chemist'. Aqui está como juntar tudo isso:

  1. Crie um novo array de apenas pessoas “chemist”, chemists, chamando filter() em people e filtrando por person.profession === 'chemist':
const chemists = people.filter(person =>
person.profession === 'chemist'
);
  1. Agora mapeie 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 fim, retorne listItems em seu 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>;
}

Pitfall

Arrow functions retornam implicitamente a expressão logo após =>, então você não precisa de uma declaração return:

const listItems = chemists.map(person =>
<li>...</li> // Retorno implícito!
);

Entretanto, você deve escrever return explicitamente se seu => é seguido de uma chave {!

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

Arrow functions contendo => { são ditas como havendo um “corpo de bloco”. Elas deixam com que você escreva mais de uma linha de código, mas você precisa escrever uma declaração return você mesmo. Caso esquecida, nada é retornado!

Mantendo itens em ordem com key

Perceba que todas as sandboxes acima exibem um erro no console:

Console
Warning: Each child in a list should have a unique “key” prop.

Você precisa dar a cada item do array uma key — uma string ou um número o qual o identifica unicamente dentre os demais itens naquele array:

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

Note

Elementos JSX diretamente dentro de um chamado map() sempre precisam de keys!

Keys dizem ao React a qual item do array cada componente corresponde, para que ele possa combiná-los mais tarde. Isso se torna importante se os itens do seu array podem se mover (ex. por ser ordenado), serem inseridos, ou serem removidos. Uma key bem escolhida ajuda o React a inferir o que exatamente aconteceu, e fazer as atualizações corretas à árvore da DOM.

Ao invés de gerar keys em tempo real, você deve incluí-las em seus dados:

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

Deep Dive

Exibindo múltiplos nós da DOM para cada item da lista

O que fazer quando cada item precisa renderizar não um, mas múltiplos nós da DOM?

O atalho de sintaxe <>...</> Fragment não deixará com que você passe uma key, então você precisa agrupá-los em uma única <div>, ou então usar a levemente mais longa e mais explícita sintaxe <Fragment>:

import { Fragment } from 'react';

// ...

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

Fragmentos desaparecem da DOM, então isto irá produzir uma lista plana de <h1>, <p>, <h1>, <p>, e assim por diante.

Onde conseguir sua key

Diferentes fontes de dados fornecem diferentes fontes de keys:

  • Dados de uma base de dados: Se seus dados estão vindo de uma base de dados, você pode usar as keys/IDs desta, os quais são únicos por natureza.
  • Dados gerados localmente: Se seus dados são gerados e persistidos localmente (ex. anotações em um aplicativo de notas), use um contador incremental, crypto.randomUUID() ou um pacote como uuid ao criar itens.

Regras das keys

  • Keys devem ser únicas dentre suas irmãs. Entretanto, é tranquilo usar a mesma keys para nós JS em arrays diferentes.
  • Keys não devem mudar ou isso derrota seu propósito! Não as gere durante a renderização.

Por que o React precisa de keys?

Imagine que os arquivos em sua área de trabalho não tivessem nomes. Em vez disso, você se referiria a eles por meio de sua ordem — o primeiro arquivo, o segundo arquivo, e assim por diante. Você poderia se acostumar com isto, porém uma vez que você exclua um arquivo, isto iria ficar confuso. O segundo arquivo se tornaria o primeiro, o terceiro arquivo se tornaria o segundo e assim por diante.

Nomes de arquivo em uma pasta e keys JSX em um array servem um propósito similar. Eles deixam com que nós identifiquemos unicamente um item entre seus irmãos. Um chave bem escolhida fornece mais informação que a posição dentro do array. Mesmo que a posição mude por conta de uma reordenação, a key permite que o React identifique o item durante seu ciclo de vida.

Pitfall

Você pode estar tentado a usar o índice de um item no array como sua key. De fato, isto é o que o React irá utilizar caso você não especifique uma key. Entretanto, a ordem em que você renderiza os itens irá mudar conforme o tempo caso um item seja inserido, excluído, ou se o array for reordenado. Usar índice como key por muitas vezes leva a bugs sutis e confusos.

Similarmente, não gere keys em tempo real, por exemplo com key={Math.random()}. Isso fará com que as keys nunca sejam iguais entre renderizações, ocasionando com que todos os seus componentes e DOM sejam recriados a cada vez. Isso não somente é lento, mas além disso perde qualquer entrada do usuário dentro dos elementos da lista. Em vez disso, use um ID estável baseado em dados.

Note que os seus componentes não receberão key como uma prop. Esta só é usada como uma dica pelo próprio React. Se o seu componente precisa de um ID, você deve passá-lo como uma prop separada: <Profile key={id} userId={id} />.

Recap

Nessa página você aprendeu:

  • Como mover dados para fora de componentes e para dentro de estruturas como arrays ou objetos.
  • Como gerar conjuntos de componentes similares com o map() do JavaScript.
  • Como criar arrays de itens filtrados com o filter() do JavaScript.
  • Porquê e como definir key em cada componente dentre uma coleção para que o React possa acompanhar cada um deles mesmo se sua posição ou dados mudem.

Challenge 1 of 4:
Separando uma lista em duas

Esse exemplo mostra uma lista de todas as pessoas.

Altere-o para exibir duas listas separadas uma após a outra: Químicos* e Qualquer Outro. Como antes, você pode determinar se uma pessoa é química checando se 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>
  );
}