É comum a necessidade de se exibir vários componentes semelhantes a partir de uma coleção de dados. Você pode usar os métodos de array JavaScript para manipular um array de dados. Nessa 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 por que usar as keys do React

Renderizando dados de arrays

Digamos que você tenha uma lista de informações.

<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 dados diferentes ao construir interfaces: de listas de comentários a galerias de fotos de perfil. Nestas 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>;

Esse é 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
Aviso: Cada filho em uma lista deve haver uma prop “key” única.

Mais tarde nessa página, você aprenderá como corrigir este erro. Mas antes, vamos estruturar os seus dados um pouco mais.

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',
}, {
id: 3,
name: 'Percy Lavon Julian',
profession: 'chemist',
}, {
id: 4,
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrophysicist',
}];

Vamos supor que você queira exibir somente as pessoas cuja profissão seja 'chemist'. Neste caso, voce pode usar o método filter() do JavaScript para retornar apenas essas pessoas. Este método recebe um array de itens, os quais são submetidos a 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 isto ficaria assim (person) => person.profession === 'chemist'. Veja como juntar tudo isso:

  1. Crie um novo array contendo apenas pessoas cuja profissão é “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>;
});

Nos referimos a arrow functions que contenham => { em seu início como possuindo um “corpo em bloco”. Elas deixam com que você escreva mais de uma linha de código, mas você precisa incluir uma declaração return manualmente. Caso esquecida, nada é retornado!

Mantendo itens em ordem com key

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

Console
Aviso: Cada filho em uma lista deve ter uma prop “key” única.

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

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

Note

Elementos JSX retornados por 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 (por exemplo, ao ser ordenado), serem inseridos, ou serem removidos. Uma key bem escolhida ajuda o React a identificar o que exatamente aconteceu, e fazer as atualizações corretas à árvore da DOM.

Em vez 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?

A sintaxe abreviada <>...</> Fragment não deixará com que você passe uma key, por conta disto você precisa agrupá-los em uma única <div>, ou então usar a um pouco 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

As chaves podem vir de diferentes fontes de dados:

  • 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 (por exemplo, anotações em um aplicativo de notas), use um contador incremental, crypto.randomUUID() ou um biblioteca como a uuid ao criar itens.

Regras das keys

  • Keys devem ser únicas entre suas irmãs. Entretanto, é tranquilo usar as mesmas chaves para nós JSX em arrays diferentes.
  • Keys não devem mudar caso contrário, isso vai contra o 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ê teria que se referir a eles pela sua ordem — o primeiro arquivo, o segundo arquivo, e assim por diante. Isto poderia funcionar em um primeiro momento, porém uma vez que você exclua um arquivo, as coisas iriam ficar confusas. 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 permitem com que nós identifiquemos unicamente um item entre seus irmãos. Uma key bem escolhida fornece mais informação que uma 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 ser 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 índices como key por muitas vezes leva a bugs sutis e confusos.

Da mesma forma, não gere keys em tempo real, por exemplo com key={Math.random()}. Isso fará com que as keys nunca sejam iguais nas renderizações subsequentes, ocasionando com que todos os seus componentes e DOM sejam recriados a cada vez. Isso não somente é lento, mas também perde qualquer dado de entrada do usuário dentro dos elementos da lista. Em vez disso, use um ID estável baseado nos 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 Outras Profissões. Como antes, você pode determinar se uma pessoa é um “químico” 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>
  );
}