Comprender las claves únicas para los hijos de la matriz en React.js

658

Estoy creando un componente React que acepta una fuente de datos JSON y crea una tabla ordenable.
Cada una de las filas de datos dinámicos tiene asignada una clave única, pero sigo recibiendo un error de:

Cada niño en una matriz debe tener un accesorio "clave" único.
Verifique el método de renderizado de TableComponent.

Mi TableComponentmétodo de renderizado devuelve:

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

El TableHeadercomponente es una sola fila y también tiene asignada una clave única.

Cada uno rowde rowsse construye a partir de un componente con una clave única:

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

Y se TableRowItemve así:

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

¿Qué está causando el error único de prop de clave?

Brett DeWoody
fuente
77
Sus filas en la matriz JS deben tener una keypropiedad única . Ayudará a ReactJS a encontrar referencias a los nodos DOM apropiados y actualizar solo el contenido dentro del marcado, pero no volver a representar toda la tabla / fila.
Kiril
¿También puedes compartir una rowsmatriz o más preferiblemente un jsfiddle? No necesita una keypropiedad en theady tbodypor cierto.
nilgun
Agregué el componente de fila a la pregunta original @nilgun.
Brett DeWoody
3
¿Es posible que algunos elementos no tengan una identificación o tengan la misma identificación?
nilgun

Respuestas:

624

Debe agregar una clave a cada elemento secundario , así como a cada elemento dentro de los elementos secundarios .

De esta forma, React puede manejar el mínimo cambio de DOM.

En su código, cada uno <TableRowItem key={item.id} data={item} columns={columnNames}/>está tratando de representar a algunos niños dentro de ellos sin una clave.

Mira este ejemplo .

Intente eliminar el key={i}del <b></b>elemento dentro de la div (y comprobar la consola).

En la muestra, si no le damos una clave al <b>elemento y queremos actualizar solo el object.city, React necesita volver a representar toda la fila frente al elemento.

Aquí está el 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);

La respuesta publicada por @Chris en la parte inferior entra en mucho más detalle que esta respuesta. Por favor, eche un vistazo a https://stackoverflow.com/a/43892905/2325522

Reaccione la documentación sobre la importancia de las claves en la reconciliación: claves

jmingov
fuente
55
Me encuentro exactamente con el mismo error. ¿Se resolvió esto después del chat? Si es así, ¿puede publicar una actualización de esta pregunta?
Deke
1
La respuesta funciona, Deke. Solo asegúrese de que el keyvalor de la utilería sea único para cada elemento y coloque la keyutilería en el componente más cercano a los límites de la matriz. Por ejemplo, en React Native, primero estaba tratando de poner keyprop al <Text>componente. Sin embargo, tuve que ponerlo en el <View>componente principal <Text>. si Matriz == [(Ver> Texto), (Ver> Texto)] necesita ponerlo a Ver. No texto
scaryguy
240
¿Por qué es tan difícil para React generar claves únicas por sí mismo?
Davor Lucic
13
@DavorLucic, aquí hay una discusión: github.com/facebook/react/issues/1342#issuecomment-39230939
koddo el
55
Esta es más o menos la palabra oficial en una nota hecha en el tema de chat vinculado anteriormente: las claves son sobre la identidad de un miembro de un conjunto y la generación automática de claves para elementos que emergen de un iterador arbitrario probablemente tenga implicaciones de rendimiento dentro de React biblioteca.
Sameers
525

¡Tenga cuidado al iterar sobre matrices!

Es un error común pensar que usar el índice del elemento en la matriz es una forma aceptable de suprimir el error con el que probablemente esté familiarizado:

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

Sin embargo, en muchos casos no lo es. Este es un antipatrón que en algunas situaciones puede conducir a un comportamiento no deseado .


Entendiendo el keyaccesorio

React utiliza la keyutilería para comprender la relación del elemento componente al elemento DOM, que luego se utiliza para el proceso de reconciliación . Por lo tanto, es muy importante que la clave siempre permanezca única , de lo contrario, hay una buena posibilidad de que React mezcle los elementos y mute el incorrecto. También es importante que estas claves permanezcan estáticas en todos los renders para mantener el mejor rendimiento.

Dicho esto, no siempre es necesario aplicar lo anterior, siempre que se sepa que la matriz es completamente estática. Sin embargo, se recomienda aplicar las mejores prácticas siempre que sea posible.

Un desarrollador de React dijo en este número de GitHub :

  • La clave no se trata realmente de rendimiento, se trata más de identidad (que a su vez conduce a un mejor rendimiento). Los valores cambiantes y asignados al azar no son identidad
  • Realmente no podemos proporcionar claves [automáticamente] sin saber cómo se modelan sus datos. Sugeriría tal vez usar algún tipo de función de hash si no tiene identificadores
  • Ya tenemos claves internas cuando usamos matrices, pero son el índice en la matriz. Cuando inserta un nuevo elemento, esas claves son incorrectas.

En resumen, a keydebería ser:

  • Único : una clave no puede ser idéntica a la de un componente hermano .
  • Estático : una clave nunca debe cambiar entre los renders.


Usando el keyaccesorio

Según la explicación anterior, estudie cuidadosamente las siguientes muestras e intente implementar, cuando sea posible, el enfoque recomendado.


Malo (potencialmente)

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

Este es posiblemente el error más común visto al iterar sobre una matriz en React. Este enfoque no es técnicamente "incorrecto" , es simplemente ... "peligroso" si no sabe lo que está haciendo. Si está iterando a través de una matriz estática, este es un enfoque perfectamente válido (por ejemplo, una matriz de enlaces en su menú de navegación). Sin embargo, si agrega, elimina, reordena o filtra elementos, debe tener cuidado. Eche un vistazo a esta explicación detallada en la documentación oficial.

En este fragmento estamos usando una matriz no estática y no nos estamos restringiendo a usarla como una pila. Este es un enfoque inseguro (verá por qué). Observe cómo a medida que agregamos elementos al comienzo de la matriz (básicamente sin desplazamiento), el valor de cada uno <input>permanece en su lugar. ¿Por qué? Porque el keyno identifica de forma única cada elemento.

En otras palabras, al principio Item 1tiene key={0}. Cuando agregamos el segundo elemento, el elemento superior se convierte Item 2, seguido por Item 1el segundo elemento. Sin embargo, ahora Item 1tiene key={1}y key={0}ya no . En cambio, Item 2ahora tiene key={0}!!

Como tal, React piensa que los <input>elementos no han cambiado, ¡porque la Itemtecla with 0siempre está en la parte superior!

Entonces, ¿por qué este enfoque solo a veces es malo?

Este enfoque solo es arriesgado si la matriz se filtra, reorganiza o se agregan / eliminan elementos. Si siempre es estático, entonces es perfectamente seguro de usar. Por ejemplo, un menú de navegación como ["Home", "Products", "Contact us"]puede iterarse de forma segura con este método porque probablemente nunca agregará nuevos enlaces ni los reorganizará.

En resumen, aquí es cuando puede usar el índice de manera segura como key:

  • La matriz es estática y nunca cambiará.
  • La matriz nunca se filtra (muestra un subconjunto de la matriz).
  • La matriz nunca se reordena.
  • La matriz se usa como una pila o LIFO (última entrada, primera salida). En otras palabras, la adición solo se puede hacer al final de la matriz (es decir, push), y solo se puede eliminar el último elemento (es decir, pop).

Si, en cambio, en el fragmento anterior, empujáramos el elemento agregado al final de la matriz, el orden de cada elemento existente siempre sería correcto.


Muy mal

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

Si bien este enfoque probablemente garantizará la unicidad de las claves, siempre obligará a reaccionar a volver a representar cada elemento de la lista, incluso cuando no sea necesario. Esta es una solución muy mala, ya que afecta en gran medida el rendimiento. Sin mencionar que no se puede excluir la posibilidad de una colisión de teclas en el caso de que Math.random()produzca el mismo número dos veces.

Las claves inestables (como las producidas por Math.random()) provocarán que muchas instancias de componentes y nodos DOM se vuelvan a crear innecesariamente, lo que puede causar degradación del rendimiento y pérdida de estado en los componentes secundarios.


Muy bien

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

Este es posiblemente el mejor enfoque porque utiliza una propiedad que es única para cada elemento del conjunto de datos. Por ejemplo, si rowscontiene datos obtenidos de una base de datos, uno podría usar la Clave primaria de la tabla ( que generalmente es un número de incremento automático ).

La mejor manera de elegir una clave es usar una cadena que identifique de forma exclusiva un elemento de la lista entre sus hermanos. La mayoría de las veces usaría ID de sus datos como claves


Bueno

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

...

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

Este también es un buen enfoque. Si su conjunto de datos no contiene datos que garanticen la unicidad ( por ejemplo, una matriz de números arbitrarios ), existe la posibilidad de una colisión de teclas. En tales casos, es mejor generar manualmente un identificador único para cada elemento del conjunto de datos antes de iterar sobre él. Preferiblemente al montar el componente o cuando se recibe el conjunto de datos ( por ejemplo, desde propso desde una llamada API asíncrona ), para hacer esto solo una vez , y no cada vez que el componente se vuelve a representar. Ya hay un puñado de bibliotecas que pueden proporcionarle esas claves. Aquí hay un ejemplo: react-key-index .

Chris
fuente
1
En los documentos oficiales , se utilizan toString()para convertir en cadena en lugar de dejar como número. ¿Es importante recordar esto?
skube
1
@skube, no, también puedes usar números enteros key. No estoy seguro de por qué lo están convirtiendo.
Chris
1
Supongo que puedes usar números enteros, pero ¿ deberías? De acuerdo con sus documentos señalan "... mejor manera de elegir una clave es usar una cadena que identifica de forma única ..." (el énfasis es mío)
Skube
2
@skube, sí, eso es perfectamente aceptable. Como se indica en los ejemplos anteriores, puede usar el índice del elemento de la matriz iterada (y eso es un número entero). Incluso los documentos indican: "Como último recurso, puede pasar el índice del elemento en la matriz como una clave" . Sin embargo, lo que sucede es que keysiempre termina siendo una Cadena de todos modos.
Chris
3
@ farmcommand2, las claves se aplican a React Components y deben ser exclusivas entre hermanos . Esto se afirma anteriormente. En otras palabras, único en el conjunto
Chris
8

Esto puede o no ayudar a alguien, pero puede ser una referencia rápida. Esto también es similar a todas las respuestas presentadas anteriormente.

Tengo muchas ubicaciones que generan listas usando la estructura a continuación:

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

Después de un poco de prueba y error (y algunas frustraciones), agregar una propiedad clave al bloque más externo lo resolvió. También tenga en cuenta que la etiqueta <> ahora se reemplaza con la etiqueta ahora.

return (

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

Por supuesto, he estado usando ingenuamente el índice iterativo (index) para completar el valor clave en el ejemplo anterior. Idealmente, usaría algo que sea exclusivo del elemento de la lista.

apil.tamang
fuente
Esto fue extremadamente útil, ¡gracias! Ni siquiera me di cuenta de que tenía que ponerlo en la capa más externa
Charles Smith
6

Advertencia: cada elemento secundario en una matriz o iterador debe tener un accesorio "clave" único.

Esta es una advertencia ya que los elementos de la matriz sobre los que vamos a iterar necesitarán un parecido único.

React maneja la representación iterativa de componentes como matrices.

Una mejor manera de resolver esto es proporcionar un índice sobre los elementos de la matriz sobre los que va a iterar, por ejemplo:

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

El índice es React props incorporado.

Shashank Malviya
fuente
2
Este enfoque es potencialmente peligroso si los elementos se reorganizan de alguna manera. Pero si permanecen estáticos, entonces está bien.
Chris
@chris Estoy completamente de acuerdo con usted porque en este caso el índice puede estar duplicado. Es mejor usar valores dinámicos para la clave.
Shashank Malviya
@chris También estoy de acuerdo con tu comentario. Deberíamos usar valores dinámicos en lugar de indexar porque puede haber duplicados. Para hacerlo simple, hice esto. Por cierto, gracias por tu contribución (votado a favor)
Shashank Malviya
6

Simplemente agregue la clave única a sus Componentes

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

Comprobar: clave = undef !!!

También recibiste el mensaje de advertencia:

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

si su código está completo bien, pero si está activado

<ObjectRow key={someValue} />

someValue es indefinido !!! Por favor verifique esto primero. Puedes ahorrar horas.

Gerd
fuente
1

La mejor solución para definir una clave única en reaccionar: dentro del mapa inicializó la publicación del nombre y luego definió la clave por clave = {post.id} o en mi código verá que defino el elemento de nombre y luego defino clave por clave = {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
fuente
0

Esta es una advertencia, pero abordar esto hará que Reacts se vuelva mucho MÁS RÁPIDO ,

Esto se Reactdebe a que necesita identificar de forma única cada elemento de la lista. Digamos que si el estado de un elemento de esa lista cambia en Reacts, Virtual DOMentonces React necesita averiguar qué elemento se cambió y en qué parte del DOM debe cambiar para que el DOM del navegador esté sincronizado con el DOM virtual de Reacts.

Como solución, simplemente introduzca un keyatributo para cada lietiqueta. Esto keydebería ser un valor único para cada elemento.

principal
fuente
Esto no es del todo correcto. El renderizado no será más rápido si agrega el keyaccesorio. Si no proporciona uno, React asignará uno automáticamente (el índice actual de la iteración).
Chris
@ Chris en ese caso, ¿por qué genera una advertencia?
primer
porque al no proporcionar una clave, React no sabe cómo se modelan sus datos. Esto puede conducir a resultados no deseados si se modifica la matriz.
Chris
@Chris en ese caso de modificación de matriz, Reaccionará corregirá los índices de acuerdo con eso si no proporcionamos claves. De todos modos, pensé que eliminar la sobrecarga adicional de React tendrá algún impacto en el proceso de renderizado.
primer
de nuevo, React básicamente lo hará key={i}. Por lo tanto, depende de los datos que contenga su matriz. Por ejemplo, si tiene la lista ["Volvo", "Tesla"], entonces, obviamente, el Volvo se identifica con la clave 0y el Tesla con 1, porque ese es el orden en que aparecerán en el bucle. Ahora, si reordena la matriz, las claves se intercambian. Para React, dado que "objeto" 0todavía está en la parte superior, más bien interpretará este cambio como un "cambio de nombre", en lugar de un reordenamiento. Las claves correctas aquí tendrían que ser, en orden, 1entonces 0. No siempre se reordena a mitad del tiempo de ejecución, pero cuando lo hace, es un riesgo.
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>
    )
  }
});

Esto aliviará el problema.

Ramesh
fuente
0

Me encontré con este mensaje de error debido a <></>que me devolvieron algunos elementos de la matriz cuando, en cambio, nulles necesario devolverlos.

Felipe
fuente
-1

Lo arreglé usando Guid para cada clave como esta: Generando 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);
}

Y luego asignando este valor a los marcadores:

{this.state.markers.map(marker => (
              <MapView.Marker
                  key={this.guid()}
                  coordinate={marker.coordinates}
                  title={marker.title}
              />
          ))}
Archil Labadze
fuente
2
¡Agregar un número aleatorio como clave es dañino! Evitará reaccionar para detectar cambios, que es el propósito de la clave.
pihentagy
-1

Básicamente, no debe usar el índice de la matriz como una clave única. En su lugar, puede usar a setTimeout(() => Date.now(),0)para establecer la clave única o un líquido no líquido o cualquier otra forma que genere una identificación única, pero no el índice de la matriz como la identificación única.

Rohan Naik
fuente