Entendendo chaves exclusivas para filhos de matriz no React.js

657

Estou criando um componente React que aceita uma fonte de dados JSON e cria uma tabela classificável.
Cada uma das linhas de dados dinâmicas possui uma chave exclusiva, mas ainda estou recebendo um erro de:

Cada criança em uma matriz deve ter um suporte "chave" exclusivo.
Verifique o método de renderização do TableComponent.

Meu TableComponentmétodo de renderização retorna:

<table>
  <thead key="thead">
    <TableHeader columns={columnNames}/>
  </thead>
  <tbody key="tbody">
    { rows }
  </tbody>
</table>

O TableHeadercomponente é uma única linha e também possui uma chave exclusiva atribuída a ele.

Cada rownos rowsé construído a partir de um componente com uma chave única:

<TableRowItem key={item.id} data={item} columns={columnNames}/>

E a TableRowItemaparência é assim:

var TableRowItem = React.createClass({
  render: function() {

    var td = function() {
        return this.props.columns.map(function(c) {
          return <td key={this.props.data[c]}>{this.props.data[c]}</td>;
        }, this);
      }.bind(this);

    return (
      <tr>{ td(this.props.item) }</tr>
    )
  }
});

O que está causando o erro exclusivo de suporte de chave?

Brett DeWoody
fonte
7
Suas linhas na matriz JS devem ter keypropriedade exclusiva . Isso ajudará o ReactJS a encontrar referências aos nós DOM apropriados e atualizar apenas o conteúdo dentro da marcação, mas não a renderizar novamente a tabela / linha inteira.
Kiril
Você também pode compartilhar rowsarray ou, de preferência, um jsfiddle? Você não precisa de um keyimóvel na theade tbodypela maneira.
Nilgun
Eu adicionei o componente de linha à pergunta original @nilgun.
Brett DeWoody
3
É possível que alguns itens não tenham um ID ou tenham o mesmo ID?
Nilgun

Respostas:

623

Você deve adicionar uma chave a cada filho e a cada elemento dentro dos filhos .

Dessa forma, o React pode lidar com a alteração mínima do DOM.

No seu código, cada um <TableRowItem key={item.id} data={item} columns={columnNames}/>está tentando renderizar alguns filhos dentro deles sem uma chave.

Veja este exemplo .

Tente remover a key={i}partir do <b></b>elemento dentro do div (e verificar o console).

No exemplo, se não fornecermos uma chave para o <b>elemento e desejarmos atualizar apenas o object.city, o React precisará renderizar novamente a linha inteira versus apenas o elemento.

Aqui está o código:

var data = [{name:'Jhon', age:28, city:'HO'},
            {name:'Onhj', age:82, city:'HN'},
            {name:'Nohj', age:41, city:'IT'}
           ];

var Hello = React.createClass({

    render: function() {

      var _data = this.props.info;
      console.log(_data);
      return(
        <div>
            {_data.map(function(object, i){
               return <div className={"row"} key={i}> 
                          {[ object.name ,
                             // remove the key
                             <b className="fosfo" key={i}> {object.city} </b> , 
                             object.age
                          ]}
                      </div>; 
             })}
        </div>
       );
    }
});

React.render(<Hello info={data} />, document.body);

A resposta postada por @Chris na parte inferior entra em muito mais detalhes do que esta resposta. Por favor, dê uma olhada em https://stackoverflow.com/a/43892905/2325522

Reagir a documentação sobre a importância das chaves na reconciliação: Chaves

jmingov
fonte
5
Estou executando exatamente o mesmo erro. Isso foi resolvido após o bate-papo? Em caso afirmativo, você pode postar uma atualização para esta pergunta.
Deke 26/02
1
A resposta funciona, Deke. Apenas certifique-se de que o keyvalor do suporte seja exclusivo para cada item e você esteja colocando o keysuporte no componente mais próximo dos limites da matriz. Por exemplo, no React Native, primeiro eu estava tentando colocar keyprop em <Text>componente. No entanto, eu tive que colocá-lo no <View>componente que é o pai <Text>. se Matriz == [(Exibir> Texto), (Exibir> Texto)], você precisará colocá-lo em Exibir. Não texto.
Scaryguy
240
Por que é tão difícil para o React gerar chaves únicas?
Davor Lucic
13
@DavorLucic, aqui está uma discussão: github.com/facebook/react/issues/1342#issuecomment-39230939
koddo 6/16
5
Essa é praticamente a palavra oficial em uma nota feita no chat de questões vinculado acima: as chaves são sobre a identidade de um membro de um conjunto e a geração automática de chaves para itens emergentes de um iterador arbitrário provavelmente tem implicações de desempenho no React biblioteca.
Sameers
524

Tenha cuidado ao iterar sobre matrizes !!

É um equívoco comum que o uso do índice do elemento na matriz seja uma maneira aceitável de suprimir o erro com o qual você provavelmente está familiarizado:

Each child in an array should have a unique "key" prop.

No entanto, em muitos casos, não é! Isso é antipadrão que, em algumas situações, pode levar a comportamentos indesejados .


Compreendendo o keysuporte

O React usa o keysuporte para entender a relação do componente para o elemento DOM, que é usada para o processo de reconciliação . Portanto, é muito importante que a chave permaneça sempre única ; caso contrário, há uma boa chance do React misturar os elementos e alterar o incorreto. Também é importante que essas chaves permaneçam estáticas durante todas as renderizações para manter o melhor desempenho.

Dito isto, nem sempre é necessário aplicar o acima, desde que se saiba que a matriz é completamente estática. No entanto, a aplicação de boas práticas é incentivada sempre que possível.

Um desenvolvedor do React disse nesta edição do GitHub :

  • A chave não é realmente sobre desempenho, é mais sobre identidade (o que, por sua vez, leva a um melhor desempenho). valores atribuídos e alterados aleatoriamente não são identidade
  • Não podemos fornecer chaves de forma realista [automaticamente] sem saber como seus dados são modelados. Eu sugeriria talvez usar algum tipo de função de hash se você não tiver ids
  • Já temos chaves internas quando usamos matrizes, mas elas são o índice na matriz. Quando você insere um novo elemento, essas chaves estão erradas.

Em suma, um keydeve ser:

  • Exclusivo - Uma chave não pode ser idêntica à de um componente irmão .
  • Estático - Uma chave nunca deve mudar entre as renderizações.


Usando o keysuporte

Conforme a explicação acima, estude cuidadosamente as seguintes amostras e tente implementar, quando possível, a abordagem recomendada.


Ruim (potencialmente)

<tbody>
    {rows.map((row, i) => {
        return <ObjectRow key={i} />;
    })}
</tbody>

Este é sem dúvida o erro mais comum visto ao iterar sobre uma matriz no React. Essa abordagem não é tecnicamente "errada" , é apenas ... "perigosa" se você não sabe o que está fazendo. Se você estiver iterando através de uma matriz estática, essa é uma abordagem perfeitamente válida (por exemplo, uma matriz de links no menu de navegação). No entanto, se você estiver adicionando, removendo, reordenando ou filtrando itens, precisará ter cuidado. Veja esta explicação detalhada na documentação oficial.

Neste trecho, estamos usando uma matriz não estática e não estamos nos restringindo a usá-la como uma pilha. Essa é uma abordagem insegura (você verá o porquê). Observe como, à medida que adicionamos itens ao início da matriz (basicamente sem deslocamento), o valor de cada um <input>permanece no local. Por quê? Porque o keynão identifica exclusivamente cada item.

Em outras palavras, a princípio Item 1tem key={0}. Quando adicionamos o segundo item, o item superior se torna Item 2, seguido por Item 1como o segundo item. No entanto, agora Item 1tem key={1}e não key={0}mais. Em vez disso, Item 2agora tem key={0}!!

Como tal, o React acha que os <input>elementos não foram alterados, porque a Itemchave with 0está sempre no topo!

Então, por que essa abordagem às vezes é ruim?

Essa abordagem é arriscada apenas se a matriz for filtrada, reorganizada ou se os itens forem adicionados / removidos. Se é sempre estático, é perfeitamente seguro de usar. Por exemplo, um menu de navegação como ["Home", "Products", "Contact us"]pode ser iterado com segurança por esse método, porque você provavelmente nunca adicionará novos links ou os reorganizará.

Em resumo, é aqui que você pode usar o índice com segurança como key:

  • A matriz é estática e nunca será alterada.
  • A matriz nunca é filtrada (exibe um subconjunto da matriz).
  • A matriz nunca é reordenada.
  • A matriz é usada como uma pilha ou LIFO (última entrada, primeira saída). Em outras palavras, a adição só pode ser feita no final da matriz (por exemplo, push), e somente o último item pode ser removido (por exemplo, pop).

Se, no snippet acima, empurrássemos o item adicionado ao final da matriz, a ordem para cada item existente sempre estaria correta.


Muito mal

<tbody>
    {rows.map((row) => {
        return <ObjectRow key={Math.random()} />;
    })}
</tbody>

Embora essa abordagem provavelmente garanta a exclusividade das chaves, ela sempre forçará a reagir para renderizar novamente cada item da lista, mesmo quando isso não for necessário. Essa é uma solução muito ruim, pois afeta muito o desempenho. Sem mencionar que não se pode excluir a possibilidade de uma colisão de chave no caso de Math.random()produzir o mesmo número duas vezes.

Chaves instáveis ​​(como as produzidas por Math.random()) farão com que muitas instâncias de componentes e nós DOM sejam recriados desnecessariamente, o que pode causar degradação no desempenho e perda de estado nos componentes filhos.


Muito bom

<tbody>
    {rows.map((row) => {
        return <ObjectRow key={row.uniqueId} />;
    })}
</tbody>

Essa é sem dúvida a melhor abordagem, pois usa uma propriedade exclusiva para cada item no conjunto de dados. Por exemplo, se rowscontiver dados buscados em um banco de dados, pode-se usar a Chave Primária da tabela ( que geralmente é um número de incremento automático ).

A melhor maneira de escolher uma chave é usar uma string que identifique exclusivamente um item da lista entre seus irmãos. Na maioria das vezes você usaria IDs dos seus dados como chaves


Boa

componentWillMount() {
  let rows = this.props.rows.map(item => { 
    return {uid: SomeLibrary.generateUniqueID(), value: item};
  });
}

...

<tbody>
    {rows.map((row) => {
        return <ObjectRow key={row.uid} />;
    })}
</tbody>

Essa também é uma boa abordagem. Se o seu conjunto de dados não contiver nenhum dado que garanta exclusividade ( por exemplo, uma matriz de números arbitrários ), é possível que haja uma colisão de chave. Nesses casos, é melhor gerar manualmente um identificador exclusivo para cada item no conjunto de dados antes de iterá-lo. De preferência, ao montar o componente ou quando o conjunto de dados é recebido ( por exemplo, de propsou de uma chamada de API assíncrona ), para fazer isso apenas uma vez , e não sempre que o componente é renderizado novamente. Já existem algumas bibliotecas disponíveis que podem fornecer essas chaves. Aqui está um exemplo: react-key-index .

Chris
fonte
1
Nos documentos oficiais , eles costumam toString()converter em sequência em vez de sair como número. Isso é importante para lembrar?
Skype #
1
@skube, não, você também pode usar números inteiros key. Não sei por que eles estão convertendo.
Chris
1
Eu acho que você pode usar números inteiros, mas deveria ? Como por seus docs eles estado "... melhor maneira de escolher uma chave é usar uma corda que identifica exclusivamente ..." (ênfase minha)
Skube
2
@skube, sim, isso é perfeitamente aceitável. Conforme declarado nos exemplos acima, você pode usar o índice do item da matriz iterada (e esse é um número inteiro). Até os documentos afirmam: "Como último recurso, você pode passar o índice do item na matriz como uma chave" . O que acontece, porém, é que o keysempre acaba sendo uma String de qualquer maneira.
Chris
3
@ farmcommand2, as chaves são aplicadas ao React Components e precisam ser únicas entre os irmãos . Isto é afirmado acima. Em outras palavras, originais na matriz
Chris
8

Isso pode ou não ajudar alguém, mas pode ser uma referência rápida. Isso também é semelhante a todas as respostas apresentadas acima.

Eu tenho muitos locais que geram lista usando a estrutura abaixo:

return (
    {myList.map(item => (
       <>
          <div class="some class"> 
             {item.someProperty} 
              ....
          </div>
       </>
     )}
 )

Após algumas tentativas e erros (e algumas frustrações), a adição de uma propriedade-chave ao bloco mais externo resolveu a questão. Observe também que a tag <> agora é substituída pela tag agora.

return (

    {myList.map((item, index) => (
       <div key={index}>
          <div class="some class"> 
             {item.someProperty} 
              ....
          </div>
       </div>
     )}
 )

Obviamente, tenho usado ingenuamente o índice de iteração (índice) para preencher o valor da chave no exemplo acima. Idealmente, você usaria algo exclusivo para o item da lista.

apil.tamang
fonte
Isso foi extremamente útil, obrigado! Eu nem percebi que tinha que colocá-lo na camada mais externa
Charles Smith
6

Aviso: Cada filho em uma matriz ou iterador deve ter um suporte "chave" exclusivo.

Este é um aviso, pois os itens da matriz sobre os quais iremos repetir precisarão de uma semelhança única.

O React manipula a renderização do componente de iteração como matrizes.

A melhor maneira de resolver isso é fornecer índice nos itens da matriz que você irá repetir. Por exemplo:

class UsersState extends Component
    {
        state = {
            users: [
                {name:"shashank", age:20},
                {name:"vardan", age:30},
                {name:"somya", age:40}
            ]
        }
    render()
        {
            return(
                    <div>
                        {
                            this.state.users.map((user, index)=>{
                                return <UserState key={index} age={user.age}>{user.name}</UserState>
                            })
                        }
                    </div>
                )
        }

O índice é React adereços internos.

Shashank Malviya
fonte
2
Essa abordagem é potencialmente perigosa se os itens forem reorganizados de alguma forma. Mas se eles permanecerem estáticos, tudo bem.
Chris
@ chris Concordo plenamente com você, porque neste caso o índice pode ser duplicado. Melhor usar valores dinâmicos para chave.
Shashank Malviya
@chris Também concordo com o seu comentário. Devemos usar valores dinâmicos em vez de indexar, pois pode haver duplicatas. Para simplificar, eu fiz isso. Btw, obrigado por sua contribuição (votado) #
Shashank Malviya
6

Basta adicionar a chave exclusiva aos seus componentes

data.map((marker)=>{
    return(
        <YourComponents 
            key={data.id}     // <----- unique key
        />
    );
})
Bashirpour
fonte
6

Verifique: chave = undef !!!

Você também recebeu a mensagem de aviso:

Each child in a list should have a unique "key" prop.

se seu código estiver completo corretamente, mas se estiver ativado

<ObjectRow key={someValue} />

someValue é indefinido !!! Por favor, verifique isso primeiro. Você pode economizar horas.

Gerd
fonte
1

A melhor solução de definir chave única em reagir: dentro do mapa, você inicializou o nome post e depois define key por key = {post.id} ou, no meu código, você vê eu defino o nome do item e depois defino key by key = {item.id }:

<div className="container">
                {posts.map(item =>(

                    <div className="card border-primary mb-3" key={item.id}>
                        <div className="card-header">{item.name}</div>
                    <div className="card-body" >
                <h4 className="card-title">{item.username}</h4>
                <p className="card-text">{item.email}</p>
                    </div>
                  </div>
                ))}
            </div>

Khadim Rana
fonte
0

Este é um aviso, mas abordar isso fará com que o Reacts torne muito mais RÁPIDO ,

Isso ocorre porque é Reactnecessário identificar exclusivamente cada item da lista. Digamos que se o estado de um elemento dessa lista for alterado no Reacts Virtual DOM, o React precisará descobrir qual elemento foi alterado e em que local do DOM ele deve ser alterado, para que o DOM do navegador esteja sincronizado com o Reacts Virtual DOM.

Como solução, basta introduzir um keyatributo para cada litag. Este keydeve ser um valor exclusivo para cada elemento.

prime
fonte
Isso não é inteiramente correto. A renderização não será mais rápida se você adicionar o keysuporte. Se você não fornecer um, o React atribuirá um automaticamente (o índice atual da iteração).
Chris
@ Chris, nesse caso, por que gerar um aviso?
prime
porque, ao não fornecer uma chave, o React não sabe como seus dados são modelados. Isso pode levar a resultados indesejados se a matriz for modificada.
Chris
@ Chris, nesse caso de modificação de array, reagirá, corrigindo os índices de acordo com isso, se não fornecermos as chaves. De qualquer forma, pensei que remover a sobrecarga extra do React causaria algum impacto no processo de renderização.
prime
novamente, o React basicamente funciona key={i}. Portanto, depende dos dados que sua matriz contém. Por exemplo, se você tiver a lista ["Volvo", "Tesla"], obviamente o Volvo é identificado pela chave 0e o Tesla com 1- porque essa é a ordem em que eles aparecerão no loop. Agora, se você reordenar a matriz, as chaves serão trocadas. Para o React, como "objeto" 0ainda está no topo, ele interpretará essa alteração como "renomear", em vez de reordenar. As chaves corretas aqui precisariam estar, em ordem, 1então 0. Você nem sempre reordena o tempo de execução, mas quando o faz, é um risco.
Chris
0
var TableRowItem = React.createClass({
  render: function() {

    var td = function() {
        return this.props.columns.map(function(c, i) {
          return <td key={i}>{this.props.data[c]}</td>;
        }, this);
      }.bind(this);

    return (
      <tr>{ td(this.props.item) }</tr>
    )
  }
});

Isso resolverá o problema.

Ramesh
fonte
0

Eu estava encontrando essa mensagem de erro por <></>ter sido retornado para alguns itens da matriz quando, em vez disso, nullprecisa ser retornado.

Felipe
fonte
-1

Corrigi isso usando o Guid para cada chave como esta: Generating Guid:

guid() {
    return this.s4() + this.s4() + '-' + this.s4() + '-' + this.s4() + '-' +
        this.s4() + '-' + this.s4() + this.s4() + this.s4();
}

s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
        .toString(16)
        .substring(1);
}

E, em seguida, atribuindo esse valor aos marcadores:

{this.state.markers.map(marker => (
              <MapView.Marker
                  key={this.guid()}
                  coordinate={marker.coordinates}
                  title={marker.title}
              />
          ))}
Archil Labadze
fonte
2
Adicionar um número aleatório como chave é prejudicial! Isso impedirá a reação para detectar alterações, que é o objetivo da chave.
pihentagy 27/03/19
-1

Basicamente, você não deve usar o índice da matriz como uma chave exclusiva. Em vez disso, você pode usar a setTimeout(() => Date.now(),0)para definir a chave exclusiva ou o uuid único ou qualquer outra maneira que gere uma identificação exclusiva, mas não o índice da matriz como a identificação exclusiva.

Rohan Naik
fonte