Inicio / Artículos

Las claves de React como identidad del componente: preservar y restablecer el estado

Las claves de React deciden qué instancia de componente sobrevive a un renderizado. Cómo las claves de índice filtran el estado entre filas, y cómo cambiar una clave reinicia intencionadamente un formulario.

Editor: REACTAPP.TOP6 min de lectura1462 palabras

Fuentes: React Keys Solve a Much Stranger Problem Than Rendering Lists

La mayoría de los desarrolladores de React añaden un key a cada llamada a .map() sin pensar demasiado en ello, y la mayoría de las explicaciones se detienen en que “React lo necesita para rastrear qué elemento ha cambiado”. Esa respuesta es correcta pero incompleta. La verdadera pregunta que React se plantea en cada renderizado es si el elemento que ve ahora es el mismo que vio la última vez, y el key es la forma en que respondes a esa pregunta. Una vez que consideras los keys como un mecanismo de identidad, los errores relacionados con index-key se vuelven predecibles, y obtienes una forma clara y declarativa de restablecer el estado de un componente según sea necesario.

La versión reflexiva

Aquí está el patrón que casi todos los repositorios de código contienen. Cada usuario en un array se convierte en un elemento User, y el id del usuario sirve como key:

users.map((user) => (
  <User key={user.id} user={user} />
))

No hay nada incorrecto en este fragmento. Lo interesante es lo que sucede cuando la clave se elige de forma inadecuada, y eso solo se manifiesta una vez que los componentes tienen su propio estado.

Una fila con estado hace visible el problema

Considere un componente de tarjeta que mantiene una bandera local selected. La bandera se encuentra dentro del componente, no en la propiedad user, por lo que React debe decidir qué instancia de tarjeta posee qué bandera en cada renderizado:

interface UserCardProps {
  user: User;
}

const UserCard = ({ user }: UserCardProps) => {
  const [selected, setSelected] = useState(false);

  return (
    // ...
  );
}

Ahora renderice a tres personas: Alice, Bob y un tercer usuario. Escrito como pares índice-elemento, el array se ve así:

0 → Alice
1 → Bob
2 → Maxime

Lo que ve React con las claves de índice

El consejo común es evitar los índices de array como claves. La razón se vuelve concreta si se observan los elementos que realmente recibe React cuando se utiliza el índice:

<User key={0} user={alice} />
<User key={1} user={bob} />
<User key={2} user={maxime} />

Supongamos que la lista permite eliminar, agregar o reordenar elementos mediante arrastrar y soltar. Si eliminamos a Alice, la siguiente renderización produce lo siguiente:

<User key={0} user={bob} />
<User key={1} user={maxime} />

Desde tu punto de vista, Alice desapareció y los otros dos se movieron hacia arriba: Bob pasó del índice 1 al índice 0, y el tercer usuario del índice 2 al 1. Desde el punto de vista de React, nada le indica eso; ve que un elemento con key={0} existía antes y sigue existiendo, con el mismo tipo de componente. El mismo tipo junto con la misma clave es exactamente la señal que React utiliza para decidir si se debe conservar una instancia. Por lo tanto, mantiene la instancia que antes mostraba a Alice y le asigna las propiedades de Bob, mantiene la segunda instancia y le asigna las propiedades del tercer usuario, y desmonta la tercera.

La consecuencia: si se seleccionó la tarjeta de Alice, ahora parece que está seleccionada la tarjeta de Bob, porque el estado selected pertenecía a la instancia con clave 0, no a Alice. Este mismo tipo de problema afecta a las entradas no controladas, al foco, a la posición de desplazamiento dentro de una fila y a cualquier otro estado vinculado a la instancia.

Por qué se escapan estos errores

Las claves incorrectas son sorprendentemente comunes en aplicaciones reales de todo tamaño. Una lista a la que solo se le añaden elementos y cuyo orden nunca cambia se comporta de manera idéntica con claves de índice y claves estables, por lo que nada parece estar roto. El error surge más tarde, cuando se añaden funciones de eliminación, filtrado, ordenamiento o reorganización. Las herramientas de análisis ayudan: la regla react/no-array-index-key de eslint-plugin-react señala las claves de índice para que la decisión se tome conscientemente y no por costumbre.

Cuando un elemento realmente no tiene un identificador estable, genéralo al crear los datos (por ejemplo, con crypto.randomUUID() en el momento de la inserción), y no durante la renderización. Una clave generada dentro de .map() cambia en cada renderización y obliga a React a volver a montar cada fila, lo cual es peor que usar el índice.

Los props describen el contenido, las claves describen la identidad

Un modelo mental compacto ayuda aquí:

  • los props responden a "¿qué contiene esto en este momento?"
  • la clave responde a "¿de qué se trata esto?"

Los props pueden cambiar libremente mientras el componente sigue siendo la misma instancia con su estado intacto. Si una fila permite edición directa, cambiar el nombre de un usuario no debería hacer que React descarte la fila y comience de nuevo; sigue siendo el mismo usuario. Una clave estable como user.id expresa precisamente eso.

Fuerzar deliberadamente una instancia nueva

A veces se desea lo contrario: que React trate un componente como si fuera completamente nuevo. Una clave también funciona fuera de las listas:

<Profile key={user.id} user={user} />

Imagínese que Profile almacena un borrador no guardado en el estado local:

interface ProfileProps {
  user: User;
}

const Profile = ({ user }: ProfileProps) => {
  const [draft, setDraft] = useState({...});
}

Si el padre cambia de un usuario a otro, llevar el borrador del usuario anterior al siguiente perfil sería un verdadero error, y potencialmente uno de fuga de datos. Sin una clave, React considera que el mismo tipo de componente se encuentra en la misma posición y mantiene la instancia, por lo que el borrador sobrevive. Con key={user.id}, un nuevo ID le indica a React que el antiguo Profile ya no existe. Desmonta la instancia anterior, descarta su estado, ejecuta las tareas de limpieza asociadas y monta una nueva cuya inicialización de useState se vuelve a ejecutar.

Esta es la alternativa idiomática a escribir un efecto que supervise user y reinicie manualmente cada parte del estado. Ese enfoque con efectos renderiza una vez con el estado obsoleto antes de corregirlo, y es fácil olvidar algún campo. Para saber más sobre por qué sincronizar el estado mediante efectos suele tener efectos negativos, consulte el patrón más seguro que reemplaza la sincronización basada en useEffect.

Reiniciar un formulario con una clave

El mismo truco permite un reinicio limpio de los formularios:

<CreateUserForm key={draft.id} />

La clave funciona como un límite de identidad. Cada vez que draft.id cambia, por ejemplo después de un envío exitoso que crea un nuevo borrador, React genera un nuevo CreateUserForm sin valores residuales de campos, errores de validación ni marcadores de que se hayan modificado.

Hay dos consideraciones importantes que vale la pena tener en cuenta. Volver a montar reinicia todo el subárbol, incluidos los hijos y cualquier dato que hayan obtenido al montarse, por lo que úselo para componentes en los que lo que se desea es un reinicio completo. Además, implica un desmontaje y montaje completos, lo cual está bien para un formulario pero no es algo que deba activarse con cada tecla pulsada.

Dónde realmente reside el estado

Todo esto se deriva de la forma en que React almacena el estado. El estado no está asociado a la función que escribiste; de ser así, cada <User /> compartiría un único valor selected. En cambio, React asocia el estado con una posición en el árbol renderizado. En cada renderización recorre la nueva salida y decide, para cada elemento, si es una continuación de algo de la renderización anterior. El tipo del componente y su posición forman parte de esa decisión, y entre elementos hermanos la clave la refina. El artículo sobre dónde mantiene su valor useState profundiza en la estructura de fibra que está detrás de esto.

La regla resultante es sencilla:

  • mismo tipo, misma posición, misma clave: React lo trata como el mismo componente y conserva su estado
  • un tipo diferente o una clave diferente: React descarta el estado anterior y crea una nueva instancia
  • Las listas son simplemente el primer lugar donde la posición deja de ser fiable. Cuando los elementos pueden moverse, aparecer o desaparecer, “el segundo hijo” ya no significa “lo mismo que la última vez”, por lo que React necesita que proporciones una identidad. Una clave estable le permite seguir un elemento a medida que la lista que lo rodea cambia; una clave modificada es una declaración explícita de que algo nuevo ha tomado su lugar.

    Puntos clave

    • Una clave le indica a React qué instancia es un elemento, no solo qué elemento ha cambiado.
    • Las claves basadas en índices vinculan el estado a una posición específica, por lo que eliminar o reordenar elementos traslada el estado a las filas incorrectas.
    • Utiliza identificadores que sean estables entre renders; nunca genere claves durante el render.
    • Cambiar una clave intencionadamente es la forma más limpia de restablecer un componente, como un formulario o un editor por entidad.
    • Use el restablecimiento por clave con moderación: reinstala todo el subárbol y descarta todo lo que hay dentro de él.