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.
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 cambiaronconst 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.
Elegir mal: síntomas y cura
Los dos cruces posibles tienen firmas de fallo opuestas y reconocibles:
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.
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, llámalo siempre<Index each={nums()}>{(n) => <li>{n()}</li>}</Index> // correcto<Index each={nums()}>{(n) => <li>{n}</li>}</Index> // muestra la funcion, no el numeroLa 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 reaccionaconst 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 nuevoconst 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?”.