Errores típicos con For e Index
Tres fallos canónicos concentran casi todo el dolor con For e Index: usar For sobre listas de primitivos, esperar una prop key al estilo React, y elegir mal entre los dos. Cada uno tiene un síntoma característico y una cura precisa. Aquí se diagnostican por sus señales y se resuelven desde la causa raíz: dónde vive la identidad.
Casi todo el sufrimiento con el control flow de listas se reduce a tres errores, y los tres comparten una misma raíz conceptual: confundir dónde vive la identidad de una fila. Usar For con primitivos, esperar una prop key que no existe, o cruzar la elección entre For e Index no son bugs de sintaxis sino de modelado. La buena noticia es que cada uno delata un síntoma inconfundible —parpadeo, foco perdido, estado que se queda pegado a la posición— y cada síntoma apunta a una cura exacta.
- Detectar el antipatrón de
Forsobre listas de primitivos y su síntoma. - Entender por qué no existe una prop
keyy qué la sustituye. - Diagnosticar la elección equivocada entre
ForeIndexpor sus señales. - Evitar los errores de firma y la mutación en sitio que engaña a ambos.
For con primitivos: el antipatrón
For clavea con ===, y los primitivos se comparan por valor. Eso rompe dos veces. Primero, dos primitivos iguales —dos 5, dos "ana"— colapsan a la misma clave, y For no sabe distinguir sus filas: avisos de clave duplicada y correspondencias erráticas. Segundo, editar un valor de 5 a 6 produce una clave nueva, así que la fila entera se recrea: parpadeo, foco perdido, estado del nodo aniquilado en cada pulsación.
const [nombres, setNombres] = createSignal(["ana", "luis"]);
// FRAGIL: al editar un nombre cambia la clave y el nodo se recrea
<For each={nombres()}>
{(n) => <input value={n} />}
</For>
// CORRECTO para primitivos editables: Index ancla el nodo
<Index each={nombres()}>
{(n) => <input value={n()} />}
</Index>
La cura tiene dos vías según lo que necesites. Si los primitivos solo cambian de valor en posiciones estables, usa Index: el nodo se queda quieto y el valor fluye por su signal. Si de verdad necesitas reordenar e identificar, entonces el problema es de datos, no de componente: envuelve cada primitivo en un objeto con id estable y usa For sobre esos objetos. For necesita referencias que seguir; dáselas o no lo uses.
Esperar una key que no existe
Quien llega de React busca por reflejo una prop key, en el For o en el hijo. No existe. Pasarla no da error —se ignora en silencio—, lo que es peor que fallar, porque te deja una falsa sensación de control.
// INUTIL: key no es una prop de For ni del hijo, se ignora
<For each={items()}>
{(item) => <li key={item.id}>{item.texto}</li>}
</For>
En Solid la clave ya está determinada por el componente: en For es la referencia de item, en Index es la posición. No hay nada que declarar. Si tu problema real era que los datos no conservan referencia entre actualizaciones —el bug que la key de React parcheaba—, la solución idiomática no es una prop mágica sino preservar identidad en la capa de estado: un createStore con reconcile, o mantener estables los objetos por id, de forma que la misma entidad conserve su referencia y For la reconozca.
import { createStore, reconcile } from "solid-js/store";
const [lista, setLista] = createStore<Item[]>([]);
// al refrescar, reconcile respeta la identidad de los que no cambiaron
const refrescar = (datos: Item[]) => setLista(reconcile(datos, { key: "id" }));
reconcile compara por el campo id y funde los datos nuevos sobre los viejos preservando las referencias de las filas que no cambiaron. Es el sustituto real de la key de React: no le dices a For cuál es la clave, le das a tus datos una identidad estable para que la referencia vuelva a ser fiable.
En React la key desambigua un diff que, por defecto, compara por posición y reconcilia árboles enteros en cada render. Solid no reconstruye descripciones ni compara árboles: el DOM real es el estado y For difea por referencia directa. La key sobra porque el puntero del objeto ya cumple su función, y Index ni siquiera la contempla porque su clave es el índice. Buscar key en Solid es traer una herramienta para una herida que aquí no se produce.
Elegir mal: síntomas y cura
Los dos cruces posibles tienen firmas de fallo opuestas y reconocibles:
Usa For cuando...
Los items son objetos con identidad estable y la lista se reordena, inserta o borra. Quieres que el nodo, su estado y su foco sigan al objeto por sus saltos de posición.
Usa Index cuando...
Los items son primitivos o campos editables y la posición es la identidad. Quieres que el nodo se ancle en su sitio y solo cambie su valor, sin remontar.
Poner For donde tocaba Index —inputs indexados por posición, primitivos que se editan— se manifiesta como inputs que pierden el foco o parpadean en cada tecla, porque cambiar el valor cambia la clave y recrea el nodo. Poner Index donde tocaba For —una lista de objetos con estado por fila que se reordena— se manifiesta como estado que se queda pegado a la posición: abres la fila del objeto B, lo mueves al final y la fila abierta sigue siendo la segunda, ahora con otro objeto, porque Index solo intercambia valores en nodos fijos.
flowchart TD Q[donde vive la identidad de la fila] --> A[en el objeto que se reordena] Q --> B[en la posicion con valores que cambian] A --> F[For clavea por referencia] B --> I[Index clavea por posicion] F --> FS[el estado y el foco siguen al objeto] I --> IS[el nodo se queda quieto y solo muta el valor] style F fill:#89b4fa,color:#11111b style I fill:#a6e3a1,color:#11111b style FS fill:#cba6f7,color:#11111b style IS fill:#cba6f7,color:#11111b
Quedan dos trampas menores que agravan las anteriores. La de firma: en For el item es un valor —escribir item() revienta—, en Index el item es un accessor —olvidar los paréntesis te da la función, no el dato—. Y la de mutación en sitio: si haces arr.push(x) y vuelves a pasar el mismo array, each no detecta cambio porque la referencia no cambió; ni For ni Index reaccionan. Entrega siempre un array nuevo, o mejor, gestiona la lista con un createStore para que la reactividad de grano fino observe las mutaciones estructuradas.
Verlo en código fija el reflejo:
// For: item es un VALOR, no lo llames como funcion
<For each={objs()}>{(o) => <li>{o.nombre}</li>}</For> // correcto
<For each={objs()}>{(o) => <li>{o().nombre}</li>}</For> // revienta: o no es funcion
// Index: item es un ACCESSOR, llamalo siempre
<Index each={nums()}>{(n) => <li>{n()}</li>}</Index> // correcto
<Index each={nums()}>{(n) => <li>{n}</li>}</Index> // muestra la funcion, no el numero
La asimetría no es capricho: en For el objeto es el invariante de la fila, por eso es un valor; en Index el valor es lo que cambia, por eso es un accessor. Si dudas de si poner paréntesis, recuerda qué clavea cada componente y la respuesta cae sola, como viste en 16.4.
La mutación en sitio: el error que engaña a los dos
Hay un cuarto fallo que golpea por igual a For y a Index, y es el más desconcertante porque el código “parece” correcto: mutar el array sin cambiar su referencia. Como each se suscribe a la identidad del array, un push, un splice o una asignación por índice sobre el mismo objeto no disparan nada. La lista en memoria cambió, pero el signal entregó la misma referencia, así que ningún consumidor se entera.
const [items, setItems] = createSignal<string[]>([]);
// ROTO: muta el mismo array, la referencia no cambia, nadie reacciona
const anadirMal = (x: string) => {
const a = items();
a.push(x); // el array es el mismo objeto
setItems(a); // setter con la MISMA referencia: sin cambio
};
// CORRECTO: entrega un array nuevo
const anadirBien = (x: string) => setItems((a) => [...a, x]);
La cura idiomática tiene dos niveles. Para listas simples, devuelve siempre un array nuevo con operaciones inmutables —spread, map, filter—. Para listas grandes o con edición fina por fila, gestiona la colección con un createStore: su reactividad de grano fino observa las mutaciones estructuradas por camino, de modo que cambiar el texto de una fila notifica solo a esa fila sin recrear el resto. Con For, además, reconcile preserva la identidad de referencia de los objetos que no cambiaron al hidratar datos nuevos, evitando recreaciones masivas.
El engaño es especialmente cruel porque el primer render funciona: los datos iniciales se muestran bien, y el bug solo aparece cuando esperas una actualización que nunca llega. Como el fallo está en la capa de datos y no en el For o el Index, revisarás el componente en vano; la pregunta correcta es siempre “¿entregué una referencia nueva?”.
Por defecto, el setter de un signal compara con === y descarta la actualización si el valor es idéntico. Pasarle el mismo array que ya tenía —aunque lo hayas mutado por dentro— es, para el signal, “no cambió nada”. Este es el origen del bug silencioso más común con listas: la mutación ocurrió, pero la notificación no. Piensa siempre en términos de reemplazo de referencia, no de mutación interna.
Foco que salta o parpadeo al editar: usaste For sobre primitivos, cambia a Index. Estado que se queda pegado a la posición tras reordenar: usaste Index sobre objetos, cambia a For. La lista no reacciona pese a que los datos cambian: mutaste en sitio, devuelve una referencia nueva o usa un store. Cada síntoma mapea a una única causa y a una única cura.
La lección que unifica los tres fallos es que ninguno es, en el fondo, un problema técnico: los tres son la misma equivocación ontológica vista desde ángulos distintos —situar la identidad de una fila donde no está—. For sobre primitivos pregunta “¿qué objeto es este 5?” cuando un 5 no es un objeto; la key de React intenta declarar una identidad que Solid ya deriva del puntero; y cruzar For con Index ata el estado al portador equivocado, al objeto cuando debía ir al lugar o al revés. Por eso las curas no son parches sino re-modelados: das identidad de referencia a lo que la necesita, dejas de declarar la que ya existe, y anclas el estado al portador correcto. Cuando interiorizas que For e Index no son dos maneras de “pintar una lista” sino dos afirmaciones sobre dónde reside la identidad, los síntomas dejan de ser misteriosos: un foco que se pierde grita “clavaste por referencia algo que vivía en la posición”, y un estado que se queda pegado grita “clavaste por posición algo que vivía en el objeto”. Diagnosticar listas se vuelve, entonces, leer esos gritos y responder con el componente que dice la verdad sobre tus datos. Esa es la maestría del control flow: no memorizar cuál usar, sino oír qué identidad reclama cada colección.
- Renderiza primitivos editables con
Fory edita uno; observa el parpadeo y el foco perdido. Cámbialo aIndexy confirma que desaparecen. - Añade una prop
keya unFory comprueba que no cambia nada; explica por qué se ignora y qué cumple su función. - Monta una lista de objetos con estado por fila usando
Index, reordénala y observa que el estado se queda en su posición. Repara conFory verifica que ahora sigue al objeto. - Fuerza los dos errores de firma —
item()enForeitem.xenIndex— y describe cada fallo. - Haz
arr.pushsin crear array nuevo y comprueba que la lista no reacciona; arréglalo devolviendo un array nuevo o con uncreateStore, y explica por qué la referencia importaba. - Simula un fetch que devuelve objetos nuevos en cada llamada y observa que
Forrecrea todo aunque las entidades sean las mismas; arréglalo conreconciley explica qué identidad recuperaste.