FNSP Web Page

A key nas listas: porque é que o índice quase nunca serve

A key não existe para o React ser rápido. Existe para ele responder a uma pergunta: este elemento é o mesmo de antes, ou é outro? Entre dois renders há duas listas, e a key é a única coisa que liga os elementos de uma aos da outra.

Quando se usa o índice como key, o que se está a dizer é «o terceiro continua a ser o terceiro». Isso é ordem, não identidade — e as duas coisas deixam de coincidir mal a lista mude.

1. O caso que se vê a olho nu

{tarefas.map((tarefa, i) => (
  <li key={i}>
    <input type="checkbox" />
    {tarefa.texto}
  </li>
))}

Marque-se a caixa da segunda tarefa e apague-se a primeira. A lista passa a ter menos um elemento, e o React compara chaves: o 0 continua a existir, o 1 continua a existir. Para ele não desapareceu nada — apenas mudou o texto de cada linha.

O resultado é que a caixa marcada fica onde estava, agora ao lado de outra tarefa. Ninguém mexeu nela; ela é que nunca foi considerada parte da linha que se apagou.

Com uma chave estável, o React vê que a linha desapareceu e desmonta-a com o que ela tinha dentro:

{tarefas.map((tarefa) => (
  <li key={tarefa.id}>
    <input type="checkbox" />
    {tarefa.texto}
  </li>
))}

2. Onde é que isto dói mais

Em tudo o que o React não controla e que vive no DOM: o valor de um campo não controlado, a posição do scroll, o foco, uma animação a meio, um vídeo a tocar. Tudo isso fica agarrado ao nó que foi reaproveitado, e o nó foi reaproveitado por causa da chave.

Numa lista só de texto, o índice não parte nada. Assim que houver estado por baixo, parte — e parte de uma forma difícil de atribuir, porque o erro aparece numa linha em que ninguém tocou.

3. Quando o índice serve mesmo

Serve, e há um caso deste site que ilustra bem a diferença. A paginação constrói uma lista que mistura números de página com reticências:

{paginas.map((p, i) =>
  p === '…' ? (
    <span key={`e${i}`}>…</span>
  ) : (
    <Link key={p} href={endereco(p)}>{p}</Link>
  )
)}

Repare nas duas chaves. A página usa o próprio número, que é a identidade dela — a página 7 é a página 7, apareça onde aparecer. As reticências usam o índice, porque umas reticências não têm identidade nenhuma: são um caractere, iguais entre si, e não guardam nada.

Daí a regra que substitui o «nunca use o índice»: o índice serve quando o elemento não tem identidade própria, não tem estado, e não contém nada que tenha estado. Falhando uma das três, não serve.

4. E quando não há id nenhum?

Primeiro, procurar melhor. Quase sempre há um campo que já é único — um slug, um caminho, um endereço de correio. Neste site as chaves são tag.id, rota.caminho, coluna.chave, opcao.valor: nenhuma foi inventada, todas já lá estavam.

Se a lista é construída no browser e os itens nascem sem identificador, gera-se um quando o item é criado:

// Sim: o id nasce com o item e acompanha-o
function acrescentar(texto) {
  setTarefas([...tarefas, { id: crypto.randomUUID(), texto }]);
}

// Não: chave nova a cada render — o React deita tudo fora e volta a montar
<li key={crypto.randomUUID()}>

Aquele segundo caso é pior do que o índice. Com o índice, o React reaproveita o que não devia; com uma chave nova de cada vez, ele destrói e reconstrói a lista inteira a cada render — e leva com ela o foco, o scroll e o que lá estivesse escrito.

5. Dois pormenores que passam despercebidos

  • A chave vai no elemento de fora do map, não num filho. Pô-la no <input> em vez de no <li> não dá erro nenhum e não serve para nada.

  • Só precisa de ser única entre irmãos, não no site todo. Duas listas diferentes podem usar as mesmas chaves sem problema — é um mal-entendido que leva muita gente a construir chaves compridas sem necessidade.

6. O outro lado: mudar a chave de propósito

Tudo isto tem um uso deliberado. Se a chave mudar, o React deita o componente fora e monta um novo — com o estado todo limpo. É a forma mais simples de repor um formulário quando se muda de registo, e já lá tinha chegado por outro caminho, no artigo dos efeitos que não são precisos.

O resumo

A key responde a «é o mesmo?». O índice responde a «está na mesma posição?». Enquanto a lista não mudar, as duas respostas coincidem — e é por isso que o erro só aparece no dia em que alguém apaga uma linha do meio.

Comentários

Ainda ninguém comentou este artigo.

Voltar ao blog

Gostávamos de saber quantas pessoas visitam o site, com o Google Analytics. Sem a sua autorização não corre nada. Política de Privacidade.