Comprendre les clés uniques pour les enfants du tableau dans React.js


658

Je construis un composant React qui accepte une source de données JSON et crée une table triable.
Chacune des lignes de données dynamiques a une clé unique qui lui est affectée mais je reçois toujours une erreur de:

Chaque enfant d'un tableau doit avoir un accessoire "clé" unique.
Vérifiez la méthode de rendu de TableComponent.

Ma TableComponentméthode de rendu renvoie:

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

Le TableHeadercomposant est une seule ligne et une clé unique lui est également attribuée.

Chaque rowen rowsest construit à partir d' un composant avec une clé unique:

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

Et le TableRowItemressemble à ceci:

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>
    )
  }
});

Quelle est la cause de l'erreur unique d'accessoire de clé?


7
Vos lignes dans le tableau JS doivent avoir une keypropriété unique . Cela aidera ReactJS à trouver des références aux nœuds DOM appropriés et à mettre à jour uniquement le contenu à l'intérieur du balisage, mais pas à restituer la table / ligne entière.
Kiril

Pouvez-vous également partager un rowstableau ou plus préférablement un jsfiddle? Vous n'avez pas besoin d'une keypropriété sur theadet tbodyd'ailleurs.
nilgun

J'ai ajouté le composant de ligne à la question d'origine @nilgun.
Brett DeWoody

3
Est-il possible que certains éléments n'aient pas d'identifiant ou aient le même identifiant?
nilgun

Réponses:


624

Vous devez ajouter une clé à chaque enfant ainsi qu'à chaque élément à l'intérieur des enfants .

De cette façon, React peut gérer le changement minimal de DOM.

Dans votre code, chacun <TableRowItem key={item.id} data={item} columns={columnNames}/>essaie de rendre certains enfants à l'intérieur d'eux sans clé.

Vérifiez cet exemple .

Essayez de retirer le key={i}de l' <b></b>élément à l' intérieur du div (et de vérifier la console).

Dans l'exemple, si nous ne donnons pas de clé à l' <b>élément et que nous voulons mettre à jour uniquement le object.city, React doit restituer la ligne entière par rapport à l'élément uniquement.

Voici le code:

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

La réponse postée par @Chris en bas est beaucoup plus détaillée que cette réponse. Veuillez consulter https://stackoverflow.com/a/43892905/2325522

Réagissez à la documentation sur l'importance des clés dans la réconciliation: Clés


5
Je rencontre exactement la même erreur. Cela a-t-il été résolu après le chat? Si oui, pouvez-vous publier une mise à jour de cette question.
Deke

1
La réponse fonctionne, Deke. Assurez-vous simplement que la keyvaleur de l'accessoire est unique pour chaque élément et que vous placez l' keyaccessoire sur le composant le plus proche des limites du tableau. Par exemple, dans React Native, j'essayais d'abord de mettre keyprop au <Text>composant. Cependant, je devais le mettre au <View>composant qui est parent de <Text>. si Array == [(Affichage> Texte), (Affichage> Texte)] vous devez le mettre en Affichage. Pas de texte.
scaryguy

240
Pourquoi est-il si difficile pour React de générer lui-même des clés uniques?
Davor Lucic


5
C'est à peu près le mot officiel sur une note faite dans le chat lié au problème ci-dessus: les clés concernent l'identité d'un membre d'un ensemble et la génération automatique de clés pour les éléments émergeant d'un itérateur arbitraire a probablement des implications en termes de performances dans React bibliothèque.
assimile le

525

Soyez prudent lors de l'itération sur les tableaux !!

Il est faux de penser que l'utilisation de l'index de l'élément dans le tableau est un moyen acceptable de supprimer l'erreur que vous connaissez probablement:

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

Cependant, dans de nombreux cas, ce n'est pas le cas! Il s'agit d'un anti-modèle qui peut dans certaines situations conduire à un comportement indésirable .


Comprendre l' keyhélice

React utilise l' keyhélice pour comprendre la relation élément-élément DOM, qui est ensuite utilisée pour le processus de réconciliation . Il est donc très important que la clé reste toujours unique , sinon il y a de fortes chances que React mélange les éléments et mute le mauvais. Il est également important que ces touches restent statiques tout au long de tous les rendus afin de maintenir les meilleures performances.

Cela étant dit, il n'est pas toujours nécessaire d'appliquer ce qui précède, à condition que l'on sache que le tableau est complètement statique. Cependant, l'application des meilleures pratiques est encouragée dans la mesure du possible.

Un développeur React a déclaré dans ce numéro GitHub :

  • la clé n'est pas vraiment une question de performance, c'est plus une question d'identité (qui à son tour conduit à de meilleures performances). les valeurs attribuées au hasard et changeantes ne sont pas une identité
  • Nous ne pouvons pas fournir de clés de manière réaliste [automatiquement] sans savoir comment vos données sont modélisées. Je suggérerais peut-être d'utiliser une sorte de fonction de hachage si vous n'avez pas d'identifiant
  • Nous avons déjà des clés internes lorsque nous utilisons des tableaux, mais ce sont les index du tableau. Lorsque vous insérez un nouvel élément, ces clés sont incorrectes.

En bref, un keydevrait être:

  • Unique - Une clé ne peut pas être identique à celle d'un composant frère .
  • Statique - Une clé ne doit jamais changer entre les rendus.


Utilisation de l' keyhélice

Selon l'explication ci-dessus, étudiez attentivement les exemples suivants et essayez de mettre en œuvre, si possible, l'approche recommandée.


Mauvais (potentiellement)

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

C'est sans doute l'erreur la plus courante lors de l'itération sur un tableau dans React. Cette approche n'est pas techniquement "mauvaise" , c'est juste ... "dangereuse" si vous ne savez pas ce que vous faites. Si vous parcourez un tableau statique, c'est une approche parfaitement valide (par exemple un tableau de liens dans votre menu de navigation). Cependant, si vous ajoutez, supprimez, réorganisez ou filtrez des éléments, vous devez être prudent. Jetez un œil à cette explication détaillée dans la documentation officielle.

Dans cet extrait, nous utilisons un tableau non statique et nous ne nous limitons pas à l'utiliser comme une pile. Il s'agit d'une approche dangereuse (vous comprendrez pourquoi). Notez que lorsque nous ajoutons des éléments au début du tableau (essentiellement non décalés), la valeur de chacun <input>reste en place. Pourquoi? Parce que le keyn'identifie pas chaque élément de manière unique.

En d' autres termes, tout d' abord Item 1a key={0}. Lorsque nous ajoutons le deuxième élément, l'élément supérieur devient Item 2, suivi Item 1du deuxième élément. Cependant, maintenant Item 1a key={1}et non key={0}plus. Au lieu de cela, a Item 2maintenant key={0}!!

En tant que tel, React pense que les <input>éléments n'ont pas changé, car la Itemtouche avec 0est toujours en haut!

Alors pourquoi cette approche n'est- elle que parfois mauvaise?

Cette approche n'est risquée que si le tableau est en quelque sorte filtré, réorganisé ou si des éléments sont ajoutés / supprimés. S'il est toujours statique, il est parfaitement sûr à utiliser. Par exemple, un menu de navigation comme ["Home", "Products", "Contact us"]peut être itéré en toute sécurité avec cette méthode, car vous n'aurez probablement jamais ajouter de nouveaux liens ou les réorganiser.

En bref, voici quand vous pouvez utiliser en toute sécurité l'index en tant que key:

  • Le tableau est statique et ne changera jamais.
  • Le tableau n'est jamais filtré (affiche un sous-ensemble du tableau).
  • Le tableau n'est jamais réorganisé.
  • Le tableau est utilisé comme une pile ou LIFO (dernier entré, premier sorti). En d'autres termes, l'ajout ne peut être effectué qu'à la fin du tableau (c'est-à-dire push), et seul le dernier élément peut être supprimé (c'est-à-dire pop).

Si, à la place, dans l'extrait ci-dessus, nous avions poussé l'élément ajouté à la fin du tableau, l'ordre de chaque élément existant serait toujours correct.


Très mauvais

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

Bien que cette approche garantisse probablement l'unicité des clés, elle obligera toujours à réagir pour restituer chaque élément de la liste, même lorsque cela n'est pas nécessaire. C'est une très mauvaise solution car elle a un impact considérable sur les performances. Sans oublier que l'on ne peut pas exclure la possibilité d'une collision de clés dans le cas où Math.random()le même numéro serait produit deux fois.

Des clés instables (comme celles produites par Math.random()) entraîneront la recréation inutile de nombreuses instances de composants et nœuds DOM, ce qui peut entraîner une dégradation des performances et une perte d'état dans les composants enfants.


très bien

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

C'est sans doute la meilleure approche car elle utilise une propriété unique pour chaque élément de l'ensemble de données. Par exemple, si rowscontient des données extraites d'une base de données, on pourrait utiliser la clé primaire de la table ( qui est généralement un numéro à incrémentation automatique ).

La meilleure façon de choisir une clé est d'utiliser une chaîne qui identifie de manière unique un élément de liste parmi ses frères et sœurs. Le plus souvent, vous utiliseriez des identifiants de vos données comme clés


Bien

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

...

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

C'est aussi une bonne approche. Si votre jeu de données ne contient aucune donnée garantissant l'unicité ( par exemple un tableau de nombres arbitraires ), il y a un risque de collision de clés. Dans de tels cas, il est préférable de générer manuellement un identifiant unique pour chaque élément de l'ensemble de données avant d'itérer dessus. De préférence lors du montage du composant ou lors de la réception de l'ensemble de données ( par exemple depuis propsou depuis un appel d'API asynchrone ), afin de le faire une seule fois , et non à chaque fois que le composant effectue un nouveau rendu. Il existe déjà une poignée de bibliothèques qui peuvent vous fournir de telles clés. Voici un exemple: react-key-index .


1
Dans les documents officiels , ils utilisent toString()pour convertir en chaîne au lieu de laisser comme nombre. Est-ce important à retenir?
skube

1
@skube, non, vous pouvez également utiliser des entiers key. Je ne sais pas pourquoi ils le convertissent.
Chris

1
Je suppose que vous pouvez utiliser des entiers, mais le devriez- vous? Selon leurs documents, ils déclarent "... la meilleure façon de choisir une clé est d'utiliser une chaîne qui identifie de manière unique ..." (c'est moi qui souligne)
skube

2
@skube, oui c'est parfaitement acceptable. Comme indiqué dans les exemples ci-dessus, vous pouvez utiliser l'index de l'élément du tableau itéré (et c'est un entier). Même les documents indiquent: "En dernier recours, vous pouvez passer l'index de l'élément dans le tableau comme clé" . Ce qui se passe cependant, c'est que le keyfinit toujours par être une chaîne de toute façon.
Chris

3
@ farmcommand2, les clés sont appliquées aux composants React et doivent être uniques parmi les frères et sœurs . Ceci est indiqué ci-dessus. En d'autres termes, unique dans la gamme
Chris

8

Cela peut ou non aider quelqu'un, mais cela peut être une référence rapide. Ceci est également similaire à toutes les réponses présentées ci-dessus.

J'ai beaucoup d'emplacements qui génèrent une liste en utilisant la structure ci-dessous:

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

Après quelques essais et erreurs (et quelques frustrations), l'ajout d'une propriété de clé au bloc le plus externe l'a résolu. Notez également que la balise <> est maintenant remplacée par la balise maintenant.

return (

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

Bien sûr, j'ai utilisé naïvement l'index d'itération (index) pour remplir la valeur de clé dans l'exemple ci-dessus. Idéalement, vous utiliseriez quelque chose qui est unique à l'élément de liste.


Cela a été extrêmement utile, merci! Je ne savais même pas que je devais le mettre dans la couche la plus externe
Charles Smith

6

Avertissement: chaque enfant d'un tableau ou d'un itérateur doit avoir un accessoire "clé" unique.

Ceci est un avertissement car pour les éléments du tableau sur lesquels nous allons répéter, il faudra une ressemblance unique.

React gère l'itération du rendu des composants sous forme de tableaux.

La meilleure façon de résoudre ce problème est de fournir un index sur les éléments du tableau sur lesquels vous allez effectuer une itération. Par exemple:

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

index est les accessoires intégrés de React.


2
Cette approche est potentiellement dangereuse si les éléments sont réorganisés d'une manière ou d'une autre. Mais s'ils restent statiques, c'est bien.
Chris

@chris Je suis entièrement d'accord avec vous car dans ce cas l'index peut être dupliqué. Mieux vaut utiliser des valeurs dynamiques pour la clé.
Shashank Malviya

@chris Je suis également d'accord avec votre commentaire. Nous devons utiliser des valeurs dynamiques plutôt qu'indexer car il peut y avoir des doublons. Pour faire simple, je l'ai fait. Btw merci pour votre contribution (vote positif)
Shashank Malviya

6

Ajoutez simplement la clé unique à vos composants

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

6

Vérifiez: key = undef !!!

Vous avez également le message d'avertissement:

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

si votre code est complet à droite, mais s'il est activé

<ObjectRow key={someValue} />

someValue n'est pas défini !!! Veuillez d'abord vérifier cela. Vous pouvez gagner des heures.


1

Meilleure solution de définir une clé unique dans react: à l'intérieur de la carte, vous avez initialisé le nom post puis key define par key = {post.id} ou dans mon code vous voyez je définis le nom item puis je définis key par 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>


0

Ceci est un avertissement, mais le traitement de ce problème rendra Reacts plus rapide ,

En effet, il Reactdoit identifier de manière unique chaque élément de la liste. Disons que si l'état d'un élément de cette liste change dans Reacts, Virtual DOMalors React doit déterminer quel élément a été modifié et où dans le DOM il doit changer pour que le DOM du navigateur soit synchronisé avec le DOM virtuel Reacts.

Comme solution, introduisez simplement un keyattribut à chaque libalise. Cela keydevrait être une valeur unique pour chaque élément.


Ce n'est pas tout à fait correct. Le rendu ne sera pas plus rapide si vous ajoutez l' keyaccessoire. Si vous n'en fournissez pas, React en attribuera un automatiquement (l'index actuel de l'itération).
Chris

@Chris dans ce cas, pourquoi déclencher un avertissement?
premier

car en ne fournissant pas de clé, React ne sait pas comment vos données sont modélisées. Cela peut conduire à des résultats indésirables si le tableau est modifié.
Chris

@Chris dans ce cas de modification de tableau, React corrigera les indices en fonction de cela si nous n'avons pas fourni de clés. Quoi qu'il en soit, je pensais que la suppression de la surcharge supplémentaire de React aurait un impact sur le processus de rendu.
premier

encore une fois, React fera l'affaire key={i}. Cela dépend donc des données que contient votre tableau. Par exemple, si vous avez la liste ["Volvo", "Tesla"], alors évidemment, la Volvo est identifiée par la clé 0et la Tesla avec 1- car c'est l'ordre dans lequel ils apparaîtront dans la boucle. Maintenant, si vous réorganisez le tableau, les clés sont échangées. Pour React, puisque "objet" 0est toujours en haut, il interprètera plutôt ce changement comme un "renommage", plutôt que comme une réorganisation. Les touches correctes ici auraient besoin d'être, dans l' ordre, 1alors 0. Vous ne réorganisez pas toujours en cours d'exécution, mais lorsque vous le faites, c'est un risque.
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>
    )
  }
});

Cela va aggraver le problème.


0

J'étais en cours d'exécution dans ce message d'erreur en raison d' <></>être retourné pour certains éléments du tableau alors qu'il nulldoit être renvoyé.


-1

J'ai corrigé cela en utilisant Guid pour chaque clé comme ceci: Génération de 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);
}

Et puis attribuer cette valeur aux marqueurs:

{this.state.markers.map(marker => (
              <MapView.Marker
                  key={this.guid()}
                  coordinate={marker.coordinates}
                  title={marker.title}
              />
          ))}

2
Ajouter un nombre aléatoire comme clé est dangereux! Il empêchera de réagir pour détecter les changements, ce qui est le but de la clé.
pihentagy

-1

Fondamentalement, vous ne devez pas utiliser l'index du tableau comme clé unique. Vous pouvez plutôt utiliser un setTimeout(() => Date.now(),0)pour définir la clé unique ou l' uuid unique ou toute autre manière qui générera un identifiant unique mais pas l'index du tableau comme identifiant unique.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.