Skip to content
This page has been auto-translated and may contain errors.View in English

Listas e chaves

Imagina que você está construindo uma lista de tarefas: alguns itens, cada um com seu próprio texto, vindos de um array de objetos todo. React não tem um componente especial de lista para transformar esse array em markup. Você usa o map do JavaScript puro para transformar cada item em um pedaço de JSX, e React renderiza o resultado.

jsx
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  )
}

Renderizando uma lista com map

Esse array todos geralmente chega como um prop passado de um componente pai que dono dos dados de verdade. todos.map percorre o array e retorna um <li> para cada todo. As chaves (curly braces) ao redor dele colocam esse array de elementos JSX direto dentro de <ul>, do mesmo jeito que você colocaria uma única expressão. Cada <li> também recebe um prop key, definido como todo.id. Essa parte não é opcional se você quer que a lista funcione corretamente: se pular isso, React ainda renderiza a lista, mas loga um aviso no console, e os bugs de reordenação descritos abaixo se tornam riscos reais na primeira vez que a lista muda de forma.

Uma chave é como React diferencia itens de lista de uma renderização para a próxima. Ela tem que ser única entre seus irmãos, então nenhuns dois <li> nessa lista compartilham uma chave, e tem que ser estável, significando que o mesmo todo sempre recebe a mesma chave toda vez que a lista é renderizada novamente. Um id vindo de seus dados, como todo.id, é exatamente isso: ele pertence ao todo, não à sua posição no array, então permanece no lugar mesmo se a lista ao redor muda.

Por que o índice do array é uma péssima chave

É por isso que o índice do array é uma péssima chave. É tentador usar, já que todo array já tem um:

jsx
{todos.map((todo, index) => (
  <li key={index}>{todo.text}</li>
))}

Isso funciona bem enquanto a lista nunca for reordenada, e nunca tiver itens inseridos ou deletados. No momento em que isso acontece, o índice para de corresponder ao item para o qual apontava. Delete o primeiro todo e cada item restante sobe um índice, então React vê as mesmas chaves anexadas a todos diferentes. Se algum desses itens da lista mantém seu próprio estado, como uma checkbox no meio de uma edição ou um input no qual alguém está digitando, esse estado fica anexado ao índice e acaba na linha errada. Trate o índice como um fallback para listas estáticas que nunca são reordenadas. Use um id real em qualquer outro lugar.

Uma boa chave vem dos dados, não da renderização. Se seus todos vêm de uma API ou um banco de dados, eles quase certamente já carregam um id: use-o diretamente em vez de derivar algo novo. Uma chave só precisa ser única entre os irmãos produzidos por aquele map específico, não em toda sua app, então uma lista de tarefas e uma lista de itens completados construída a partir dos mesmos dados podem ambas usar todo.id como chave sem colisão. React apenas compara chaves dentro de um único conjunto de filhos por vez.

A chave também tem que estar no elemento que map retorna diretamente, não em algo aninhado dentro dele:

jsx
// errado: a chave está em um elemento interno, então React nunca a vê
{todos.map(todo => (
  <li>
    <span key={todo.id}>{todo.text}</span>
  </li>
))}

// correto: a chave está no elemento mais externo que o callback retorna
{todos.map(todo => (
  <li key={todo.id}>{todo.text}</li>
))}

React lê as chaves do elemento de nível superior de cada item no array. Uma chave enterrada dentro de um elemento filho não conta, e você ainda vai receber o aviso "cada filho em uma lista deveria ter uma chave única" mesmo que uma chave exista em algum lugar no JSX.

O índice nem sempre está errado. Para uma lista que é renderizada uma vez e nunca é reordenada, filtrada ou dividida, um conjunto de links estáticos em um rodapé, por exemplo, key={index} funciona bem porque o índice e o item para o qual aponta nunca se afastam. O problema começa no momento em que essa lista pode mudar de forma: reordenar linhas, filtrar um resultado de busca conforme alguém digita, deletar um item. Nesse ponto o índice começa a apontar para dados diferentes do que apontava na última renderização, e é quando estado e nós do DOM ficam anexados à linha errada.

Às vezes não há um id nos dados, digamos um array simples de strings, ou um array construído a partir de um cálculo sem identificador natural. Nesse caso, construa uma chave composta a partir de qualquer combinação de campos que seja realmente estável e única para aquela lista, como ${todo.category}-${todo.text}, em vez de usar o índice por padrão.

Chaves são o que fazem a reconciliação do React funcionar corretamente em uma lista. Quando um componente renderiza novamente, React compara a nova lista de elementos com a antiga para descobrir o conjunto mínimo de mudanças no DOM necessárias, e usa a chave para corresponder elementos através daquela comparação. A mesma chave em ambas as renderizações significa que React a trata como o mesmo elemento: atualiza no lugar e mantém seu nó no DOM, seu estado interno, e qualquer outra coisa anexada a ele. Nenhuma chave correspondente na renderização antiga significa que React a trata como nova e monta fresca. Uma chave que desaparece entre renderizações significa que React desmonta aquele elemento e descarta seu estado.

É isso que realmente quebra quando a chave está errada. Digamos que um item de lista escondido atrás de uma chave de índice tenha seu próprio estado, uma flag "expandido" em uma linha de acordeão, por exemplo. Reordene o array subjacente sem mudar as chaves, e React ainda corresponde o índice antigo 2 ao índice novo 2. Ele vê "o mesmo elemento", reutiliza aquele nó do DOM e seu estado, e passa a flag expandida para seja qual for o todo que agora esteja no índice 2. Nada lança uma exceção, nada avisa você no console. Linhas silenciosamente mostram o estado errado, e quem estiver debugando dificilmente suspeita de um problema de chave no primeiro momento. Um id estável contorna isso porque se move com os dados, não com a posição no array, então reconciliação corresponde o elemento certo ao item certo não importa como a lista seja reordenada.

JunoChaves dizem ao React qual item é qual Quando você transforma um array em uma lista de elementos com map, dê a cada um uma key, e use um id real de seus dados, não sua posição no array. A chave é como React acompanha qual elemento é qual entre renderizações, e um id que pertence ao item permanece correto mesmo se a lista for reordenada.
JunoChaves dizem ao React qual item é qual Renderizar uma lista é array.map retornando JSX, com uma key no elemento mais externo de cada item. Use um id estável de seus dados. O índice do array parece um atalho, mas quebra assim que a lista pode ser reordenada, inserida ou deletada, porque o índice não mais se alinha com o mesmo item subjacente, e estado pode acabar anexado à linha errada.
JunoChaves dizem ao React qual item é qual Chaves são a identidade que React usa para reconciliar uma lista entre renderizações: mesma chave, mesmo elemento, estado e nó do DOM são mantidos; chave faltando, desmonta. Uma chave de índice só é segura para uma lista que nunca muda. Qualquer coisa que reordene, insira ou delete precisa de um id real e estável, ou você vai ter estado silenciosamente anexado à linha errada sem nenhum erro para apontar para isso.

Próximo: Estado, onde um componente começa a lembrar das coisas entre renderizações.