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

Listas y claves

Supongamos que estás construyendo una lista de tareas: algunos elementos, cada uno con su propio texto, extraídos de un array de objetos todo. React no tiene un componente especial de lista para convertir ese array en markup. Usas el map de JavaScript puro para convertir cada elemento en un fragmento de JSX, y React renderiza los resultados.

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

Renderizar una lista con map

Ese array todos generalmente llega como un prop pasado desde un componente padre que posee los datos reales. todos.map recorre el array y devuelve un <li> para cada todo. Las llaves alrededor de él colocan ese array de elementos JSX directamente en el <ul>, de la misma manera que colocarías una sola expresión. Cada <li> también recibe un prop key, establecido en todo.id. Esa parte no es opcional si quieres que la lista se comporte correctamente: si la omites, React sigue renderizando la lista, pero registra una advertencia en la consola, y los bugs de reordenamiento descritos a continuación se vuelven riesgos reales la primera vez que la lista cambia de forma.

Una clave es cómo React distingue los elementos de una lista de un renderizado al siguiente. Tiene que ser única entre sus hermanos, así que ningos dos elementos <li> en esta lista comparten una clave, y tiene que ser estable, lo que significa que el mismo todo obtiene la misma clave cada vez que la lista se renderiza de nuevo. Un id de tus datos, como todo.id, es exactamente eso: pertenece al todo, no a su posición en el array, así que se mantiene en su lugar incluso si la lista alrededor cambia.

Por qué el índice del array es una mala clave

Ese último punto es por qué el índice del array es una mala clave. Es tentador, ya que cada array ya tiene uno:

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

Esto funciona bien mientras la lista nunca se reordene, y nunca tenga elementos insertados o eliminados. En el momento en que lo hace, el índice deja de coincidir con el elemento al que solía apuntar. Elimina el primer todo y cada elemento restante se desplaza un índice hacia arriba, así que React ve las mismas claves adjuntas a diferentes todos. Si alguno de esos elementos de la lista mantiene su propio estado, como un checkbox a mitad de edición o un input donde alguien está escribiendo, ese estado se mantiene adjunto al índice y termina en la fila incorrecta. Trata el índice como un respaldo para listas que son estáticas y nunca se reordenan. Usa un id real en todos los demás casos.

Una buena clave viene de los datos, no del renderizado. Si tus todos provienen de una API o una base de datos, casi con seguridad ya llevan un id: úsalo directamente en lugar de derivar algo nuevo. Una clave solo necesita ser única entre los hermanos producidos por esa única llamada a map, no en toda tu aplicación, así que una lista de tareas y una lista de elementos completados construidas a partir de los mismos datos pueden ambas usar todo.id como clave sin conflicto. React solo compara claves dentro de un único conjunto de hijos a la vez.

La clave también tiene que estar en el elemento que map devuelve directamente, no en algo anidado dentro de él:

jsx
// incorrecto: la clave está en un elemento interno, así que React nunca la ve
{todos.map(todo => (
  <li>
    <span key={todo.id}>{todo.text}</span>
  </li>
))}

// correcto: la clave está en el elemento más externo que el callback devuelve
{todos.map(todo => (
  <li key={todo.id}>{todo.text}</li>
))}

React lee las claves del elemento de nivel superior de cada elemento en el array. Una clave enterrada dentro de un elemento hijo no cuenta, y seguirás recibiendo la advertencia "each child in a list should have a unique key" incluso aunque una clave exista en algún lugar del JSX.

El índice tampoco siempre es incorrecto. Para una lista que se renderiza una sola vez y nunca se reordena, filtra o modifica, como un conjunto de enlaces estáticos en un pie de página, key={index} funciona bien porque el índice y el elemento al que apunta nunca se desvían. El problema comienza en el momento en que esa lista puede cambiar de forma: reordenar filas, filtrar un resultado de búsqueda mientras alguien escribe, eliminar un elemento. En ese punto el índice comienza a apuntar a datos diferentes de los que apuntaba en el renderizado anterior, y es entonces cuando el estado y los nodos del DOM quedan adjuntos a la fila incorrecta.

A veces no hay un id en los datos en absoluto, digamos un array simple de strings, o un array construido a partir de un cálculo sin identificador natural. En ese caso, construye una clave compuesta a partir de cualquier combinación de campos que sea realmente estable e única para esa lista, como ${todo.category}-${todo.text}, en lugar de optar por defecto al índice.

Las claves son lo que hace que la reconciliación de React funcione correctamente en una lista. Cuando un componente se renderiza de nuevo, React compara la nueva lista de elementos con la anterior para determinar el conjunto mínimo de cambios del DOM necesarios, y usa la clave para emparejar elementos a través de esa comparación. La misma clave en ambos renderizados significa que React la trata como el mismo elemento: la actualiza en su lugar y mantiene su nodo del DOM, su estado interno y cualquier otra cosa adjunta a ella. No hay clave coincidente en el renderizado anterior significa que React la trata como nueva y la monta desde cero. Una clave que desaparece entre renderizados significa que React desmonta ese elemento y desecha su estado.

Esto es lo que realmente se rompe cuando la clave es incorrecta. Digamos que un elemento de lista oculto detrás de una clave de índice mantiene su propio estado, una bandera "expanded" en una fila de acordeón, por ejemplo. Reordena el array subyacente sin cambiar las claves, y React sigue emparejando el índice anterior 2 con el índice nuevo 2. Ve "el mismo elemento", reutiliza ese nodo del DOM y su estado, y le pasa la bandera expanded a cualquier todo que ahora se encuentre en el índice 2. Nada lanza un error, nada te advierte en la consola. Las filas silenciosamente muestran el estado incorrecto, y quien esté depurándolo raramente sospecha un problema de clave al principio. Un id estable evita esto porque se mueve con los datos, no con la posición del array, así que la reconciliación empareja el elemento correcto con el elemento correcto sin importar cómo se reordene la lista.

JunoLas claves le dicen a React cuál elemento es cuál Cuando conviertes un array en una lista de elementos con map, dale a cada uno una key, y usa un id real de tus datos, no su posición en el array.

La clave es cómo React mantiene un registro de cuál elemento es cuál entre renderizados, y un id que pertenece al elemento se mantiene correcto incluso si la lista se reordena.

JunoLas claves le dicen a React cuál elemento es cuál Renderizar una lista es array.map devolviendo JSX, con una key en el elemento más externo de cada elemento. Usa un id estable de tus datos.

El índice del array se ve como un atajo, pero se rompe en cuanto la lista pueda reordenarse, insertarse o eliminarse, porque el índice ya no se alinea con el mismo elemento subyacente, y el estado puede terminar adjunto a la fila incorrecta.

JunoLas claves le dicen a React cuál elemento es cuál Las claves son la identidad que React usa para reconciliar una lista a través de renderizados: misma clave, mismo elemento, estado y nodo del DOM llevados; clave faltante, desmontar.

Una clave de índice es segura solo para una lista que nunca cambia. Cualquier cosa que se reordene, inserte o elimine necesita un id real y estable, o terminarás con estado silenciosamente adjunto a la fila incorrecta sin ningún error que te lo señale.

Siguiente: Estado, donde un componente comienza a recordar cosas entre renderizados.