Arrays en stores: acceso, For y reactividad por índice
Un array dentro de un store es otra rama proxy: accedes con estado.items[i].campo, escribes por posición con setEstado items i campo valor, y añades por longitud o con un actualizador. Iterar con For conserva la identidad de cada elemento, así que editar un campo por ruta actualiza esa fila sin remontarla; mapear con objetos nuevos, en cambio, la remonta. La reactividad distingue longitud, posición y hoja.
Arrays como ramas del store
El array vive dentro del proxy, así que cada elemento y cada campo de cada elemento es su propia hoja rastreada. Accedes como accederías a cualquier estructura, y la lectura suscribe con la misma finura: leer estado.items[1].hecha te ata a esa hoja, no al array entero.
import { createStore } from "solid-js/store";const [estado, setEstado] = createStore({ items: [ { id: 1, texto: "a", hecha: false }, { id: 2, texto: "b", hecha: false }, ],});estado.items[1].texto; // "b" — hoja concretaestado.items.length; // 2 — la longitud también es reactivaLa escritura por ruta se extiende a los índices y ofrece formas potentes de tocar varios elementos a la vez sin recorrer el array a mano. Puedes escribir una posición concreta, un conjunto de índices, un rango o un predicado que selecciona los elementos a modificar.
// Por posición y camposetEstado("items", 1, "hecha", true);// Añadir al final: escribe en el índice igual a la longitud actualsetEstado("items", estado.items.length, { id: 3, texto: "c", hecha: false });// Varios índices a la vezsetEstado("items", [0, 1], "hecha", true);// Por predicado: selecciona qué elementos y escribe su camposetEstado("items", (t) => !t.hecha, "hecha", true);// Sobre el array entero, con un actualizador que produce referencia nuevasetEstado("items", (arr) => arr.filter((t) => t.id !== 1));Las formas por índice, por lista de índices, por rango y por predicado son escrituras quirúrgicas: tocan solo las hojas seleccionadas y conservan intacto todo lo demás. El actualizador sobre el array entero es la excepción y merece cuidado, como veremos enseguida.
Añadir y quitar merecen una nota aparte, porque son cambios estructurales del array: tocan su longitud, no solo una hoja. Añadir escribe en el índice igual a la longitud actual; quitar y reordenar reemplazan el array con una referencia nueva mediante un actualizador que produce el array resultante.
// Quitar por posición: reemplazo estructural con referencia nuevasetEstado("items", (arr) => arr.filter((_, i) => i !== 1));// Reordenar: For moverá las filas existentes en lugar de recrearlassetEstado("items", (arr) => [arr[1], arr[0], ...arr.slice(2)]);El anidamiento se comporta igual, sin límite de profundidad: si cada elemento tiene su propio array de subetiquetas, estado.items[i].subtags[j] es otra hoja rastreada, y setEstado("items", i, "subtags", j, valor) la escribe con la misma precisión. La reactividad de grano fino no se detiene en el primer array; recorre el árbol entero, arrays incluidos, hasta la hoja que nombres.
Iterar con For sobre un array del store
<For> es el componente de iteración correcto para arrays de objetos, y lo es precisamente por cómo se lleva con los stores. <For> empareja cada elemento con su fila por referencia: mientras la identidad de un elemento no cambie, su fila no se recrea. Y como el store conserva la identidad de cada elemento cuando editas un campo por ruta, editar un campo actualiza solo el binding afectado dentro de la fila, sin remontar el nodo.
import { For } from "solid-js";<For each={estado.items}> {(item) => ( <li classList={{ hecha: item.hecha }}>{item.texto}</li> )}</For>Cuando ejecutas setEstado("items", 1, "hecha", true), el elemento de índice 1 sigue siendo el mismo proxy que antes: solo cambió su hoja hecha. <For> no ve un elemento nuevo, así que no toca el <li>; únicamente el binding de classList, suscrito a item.hecha, se reejecuta. Una lista de mil filas donde marcas una casilla actualiza exactamente una casilla.
Reactividad por índice: longitud, posición y hoja
Aquí conviene separar tres suscripciones distintas que a menudo se confunden. Leer estado.items.length te suscribe a la longitud: añadir o quitar elementos te despierta, pero editar un campo de un elemento existente no. Leer estado.items[i] te suscribe a la posición i: te despierta si esa ranura pasa a apuntar a otro elemento. Leer estado.items[i].campo te suscribe a la hoja: te despierta solo si ese campo concreto cambia.
Esa distinción es la que separa a <For> de <Index>. <For> está pensado para listas de objetos con identidad estable: mapea por referencia, y por eso reordenar mueve filas sin recrearlas. <Index> mapea por posición: la fila número i es fija y lo que cambia es su contenido, que recibes como un accessor. <Index> es la elección correcta cuando el array es de primitivas —donde no hay identidad de referencia que preservar— o cuando la posición es la semántica, como en una fila de inputs fijos.
import { Index } from "solid-js";// Index: la fila es la posición; el valor llega como accessor<Index each={estado.items}> {(item, i) => <li>{item().texto}</li>}</Index>Nota una asimetría reveladora en las firmas de ambos. En <For> el callback recibe el elemento como valor directo y el índice como un signal —(item, indice) => ...—, porque el elemento es estable y su posición puede cambiar. En <Index> es al revés: el índice es un número fijo y el valor llega como accessor —(item, indice) => item()—, porque la posición es estable y el contenido puede cambiar. La forma de cada firma te recuerda cuál de los dos ejes es el que permanece fijo.
Elegir mal entre <For> e <Index> no rompe la aplicación, pero degrada su comportamiento: <Index> sobre objetos con identidad reordena recreando contenido en vez de mover filas, y <For> sobre primitivas que se repiten trata valores iguales como colisiones de clave. La brújula: identidad estable y reordenamientos, <For>; posición fija o primitivas, <Index>.