¿Para qué sirven las keys cuando renderizas una lista en React y por qué no se recomienda usar el índice?
Ver respuesta — intenta responderla en voz alta primero
La key es un identificador estable que le dice a React qué elemento de la lista es cuál entre un render y otro. Con keys correctas, React sabe si un elemento se movió, se agregó o se eliminó. Usar el índice como key falla cuando la lista se reordena, se filtra o se insertan elementos, porque el índice cambia de dueño.
En React las listas se renderizan con map dentro del JSX: transformas un arreglo de datos en un arreglo de elementos, y cada uno necesita una prop key. ¿Por qué? Cuando la lista cambia, React compara la lista nueva con la anterior; sin una identidad clara, solo puede comparar por posición. La key le da identidad real a cada elemento:
- Key estable y única (un
iddel dato): React empareja correctamente cada elemento aunque cambie de posición. Conserva su estado (inputs, foco, animaciones) y solo mueve el nodo. - Índice como key: la key describe la posición, no el dato. Si eliminas el primer elemento, todos los demás "heredan" el índice del anterior y React cree que solo cambió el contenido. El síntoma clásico: borras una fila y el texto de un input "se cruza" a la fila equivocada.
Regla práctica: usa el id del dato. El índice solo es aceptable si la lista es estática (nunca se reordena, filtra ni inserta) y sus elementos no tienen estado.
import { useState } from "react";
type Tarea = { id: string; titulo: string }; // el id es la identidad estable
function ListaTareas() {
const [tareas, setTareas] = useState<Tarea[]>([
{ id: "a1", titulo: "Estudiar hooks" },
{ id: "b2", titulo: "Practicar TypeScript" },
]);
const eliminar = (id: string) =>
setTareas((prev) => prev.filter((tarea) => tarea.id !== id));
return (
<ul>
{/* map convierte datos en elementos; cada uno lleva su key */}
{tareas.map((tarea) => (
<li key={tarea.id}>
{tarea.titulo}
{/* El input conserva su valor: la key identifica a la tarea */}
<input placeholder="notas" />
<button onClick={() => eliminar(tarea.id)}>Eliminar</button>
</li>
))}
</ul>
);
}
// Comportamiento: escribe "hola" en las notas de la segunda tarea y elimina
// la primera. Con key={tarea.id}, "hola" sigue en su fila; con el índice no.
Usar el índice del map como key en una lista que cambia:
// MAL: la key es la posición, no la identidad del dato.
// Si eliminas la tarea 0, la que era 1 ahora tiene key 0: React reutiliza
// el <li> viejo y el input conserva el texto equivocado.
{tareas.map((tarea, indice) => (
<li key={indice}>{tarea.titulo}<input placeholder="notas" /></li>
))}
// BIEN: la key acompaña al dato aunque cambie de posición
{tareas.map((tarea) => (
<li key={tarea.id}>{tarea.titulo}<input placeholder="notas" /></li>
))}
Si tus datos no traen id, genera uno al crearlos (por ejemplo con crypto.randomUUID()), nunca durante el render.
"Las keys le dan identidad a cada elemento de una lista para que React pueda emparejarlos entre renders. Con una key estable, como el id del dato, React sabe si un elemento se movió, se insertó o se eliminó, y conserva su estado y su nodo del DOM. El índice como key es peligroso porque representa la posición: si la lista se reordena o filtro elementos, los índices cambian de dueño y aparecen bugs como inputs cuyo texto se cruza a la fila equivocada. Por eso uso el id de los datos y solo aceptaría el índice en listas totalmente estáticas."
Este componente tiene un bug de keys: si marcas el checkbox del primer producto y luego lo eliminas, el check "se pasa" al siguiente. Encuentra el problema y corrígelo.
type Producto = { id: number; nombre: string };
function Carrito({ productos }: { productos: Producto[] }) {
return (
<ul>
{productos.map((producto, i) => (
<li key={i}><input type="checkbox" /> {producto.nombre}</li>
))}
</ul>
);
}
Ver solución
function Carrito({ productos }: { productos: Producto[] }) {
return (
<ul>
{productos.map((producto) => (
// La key ahora es la identidad del producto, no su posición.
// Al eliminar un elemento, cada checkbox sigue con su producto.
<li key={producto.id}><input type="checkbox" /> {producto.nombre}</li>
))}
</ul>
);
}