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

Listas y keys

Imagina que estás construyendo una lista de tareas: varios elementos, cada uno con su propio texto, sacados de un array de objetos todo. React no tiene un componente especial para convertir ese array en markup. Usas el map de JavaScript para convertir cada elemento en un pedazo 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 una prop pasada desde un componente padre que es dueño de los datos reales. todos.map recorre el array y devuelve un <li> para cada todo. Las llaves alrededor lo sueltan directamente en el <ul>, igual que soltarías una sola expresión. Cada <li> también recibe una prop key, establecida a todo.id. Esa parte no es opcional si quieres que la lista funcione correctamente: omítela, y React igual renderiza la lista, pero registra una advertencia en la consola, y los bugs de reorden que se describen abajo se vuelven riesgos reales la primera vez que la lista cambia de forma.

Una key 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 <li> en esta lista comparten una key, y tiene que ser estable, lo que significa que el mismo todo obtiene la misma key cada vez que la lista se rerenderiza. 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 a su alrededor cambia.

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

Ese último punto es por qué el índice del array es una mala key. 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 haya 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 sube un índice, así que React ve las mismas keys adjuntas a diferentes todos. Si alguno de esos elementos de la lista sostiene su propio estado, como un checkbox a mitad de una edición o un input en el que alguien está escribiendo, ese estado permanece adjunto al índice y termina en la fila equivocada. Trata el índice como un respaldo para listas estáticas que nunca se reordenan. Usa una key real en cualquier otro lugar.

Una buena key viene de los datos, no del renderizado. Si tus todos vienen de una API o una base de datos, casi con seguridad ya tienen un id: úsalo directamente en lugar de derivar algo nuevo. Una key solo necesita ser única entre los hermanos producidos por ese map en particular, 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 key sin conflicto. React solo compara keys dentro de un único conjunto de hijos a la vez.

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

jsx
// incorrecto: la key 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 key está en el elemento más externo que el callback devuelve
{todos.map(todo => (
  <li key={todo.id}>{todo.text}</li>
))}

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

El índice tampoco está siempre mal. Para una lista que se renderiza una vez y nunca se reordena, filtra o divide, como un conjunto de enlaces estáticos en un pie de página, por ejemplo, key={index} funciona bien porque el índice y el elemento al que apunta nunca se separan. 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 render anterior, y es entonces cuando el estado y los nodos del DOM se adjuntan a la fila equivocada.

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 un identificador natural. En ese caso, construye una key 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 defaultear al índice.

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

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

JunoLas keys le dicen a React cuál 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 key es cómo React mantiene un registro de cuál es cuál entre renderizados, y un id que pertenece al elemento se mantiene correcto incluso si la lista se reordena.
JunoLas keys le dicen a React cuál es cuál Renderizar una lista es array.map devolviendo JSX, con una key en el elemento más externo de cada elemento. Busca un id estable de tus datos. El índice del array se ve como un atajo, pero se rompe en el momento en que la lista puede 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 equivocada.
JunoLas keys le dicen a React cuál es cuál Las keys son la identidad que React usa para reconciliar una lista entre renderizados: misma key, mismo elemento, estado y nodo del DOM trasladados; key faltante, desmontar. Una key de índice solo es segura para una lista que nunca cambia. Cualquier cosa que se reordene, inserte o elimine necesita un id real y estable, o terminarás con el estado silenciosamente adjunto a la fila equivocada sin ningún error que te lo indique.

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