modifyMutable, reconcile y la disciplina de un solo canal de escritura
reconcile fusiona datos externos conservando la identidad de las filas; modifyMutable aplica reconcile o produce sobre la raíz de un store en un único lote. Juntos con rutas, actualizadores y produce forman un flujo coherente. La regla que lo sostiene todo: el proxy es de solo lectura y jamás se muta fuera del setter, porque una mutación clandestina es invisible para el grafo.
reconcile: fusionar lo externo sin remontar
Cuando revalidas datos de red, recibes un árbol nuevo con contenido casi idéntico pero referencias completamente frescas. Reemplazar sin más haría que Solid considerara cambiado todo, remontando cada fila de cada For. reconcile es el modificador que evita el desastre: recorre el árbol viejo y el nuevo emparejando por clave, y escribe solo en las hojas que de verdad difieren, dejando intactas —con su misma identidad— las que coinciden.
import { createStore, reconcile } from "solid-js/store";const [estado, setEstado] = createStore({ tareas: [] as Tarea[] });async function revalidar() { const frescas = await fetch("/api/tareas").then((r) => r.json()); // Fusiona por id: filas sin cambios conservan su identidad y no remontan setEstado("tareas", reconcile(frescas, { key: "id" }));}La opción key —"id" por defecto— le dice a reconcile cómo reconocer que dos elementos son «el mismo» pese a ser referencias distintas. Con eso, una fila cuyo contenido no cambió no se toca, sus efectos no se disparan y su nodo del DOM permanece; solo las filas nuevas, borradas o realmente modificadas mueven algo. Es lo que hace que un For sobreviva al cruce con el mundo exterior de los datos remotos sin parpadear.
modifyMutable: un modificador sobre la raíz
setStore(reconcile(...)) y setStore(produce(...)) aplican un modificador a la raíz de un store creado con createStore. Pero existe una segunda variante de store, createMutable, que no tiene setter: se muta directamente. Para aplicarle un modificador —reconciliar su raíz, o agrupar muchas mutaciones en un lote— usas modifyMutable.
import { createMutable, modifyMutable, reconcile, produce } from "solid-js/store";const estado = createMutable({ usuario: { nombre: "Ada" }, tareas: [] });// Reconciliar toda la raiz desde el servidor, en un solo lotemodifyMutable(estado, reconcile(desdeServidor, { key: "id" }));// Agrupar varias mutaciones del mutable en una unica propagacionmodifyMutable(estado, produce((s) => { s.usuario.nombre = "Grace"; s.tareas.push(nueva);}));modifyMutable(store, modificador) toma el store y le aplica reconcile o produce sobre la raíz, todo dentro de un batch implícito para que la ristra de cambios se propague una sola vez. Es la herramienta que reconcilia un store mutable —que de otro modo solo aceptaría mutaciones sueltas, cada una con su propia propagación— y también la vía limpia para reemplazar la raíz entera de forma diferencial, algo que el setter por ruta, al fusionar en la raíz, no te da directamente.
Con un createStore normal tienes el equivalente directo pasando el modificador al propio setter, sin modifyMutable:
// createStore: reconcile en la raiz a traves del settersetEstado(reconcile(estadoCompletoDelServidor, { key: "id" }));// createMutable: el mismo efecto, via modifyMutable porque no hay settermodifyMutable(estado, reconcile(estadoCompletoDelServidor, { key: "id" }));La equivalencia deja claro el reparto de papeles: modifyMutable es a createMutable lo que setStore(modificador) es a createStore. Elige el par según cómo creaste el store, pero recuerda que el modificador —reconcile o produce— es idéntico en ambos mundos.
Combinar las técnicas en un flujo
Ninguna de las cinco herramientas compite con las demás; cada una habita una fase distinta del ciclo de vida del estado. Un componente real las usa todas sin fricción: reconcile para ingerir, produce para editar en bloque, la ruta para tocar un campo, el actualizador para derivar.
// Ingesta desde el servidor: reconcile preserva identidadsetEstado("tareas", reconcile(await cargar(), { key: "id" }));// Edicion compuesta local: producesetEstado(produce((s) => { s.filtro = "pendientes"; s.tareas.forEach((t) => { if (t.vencida) t.prioridad = "alta"; });}));// Toque puntual: rutasetEstado("tareas", (t) => t.id === 7, "hecha", true);// Derivar del previo: actualizadorsetEstado("contadorVistas", (n) => n + 1);El criterio de selección ya lo tienes destilado: una hoja, ruta; deriva del previo, actualizador; varios campos con lógica, produce; un árbol de fuera, reconcile; la raíz de un mutable, modifyMutable. No memorices casos, lee la forma del cambio y la herramienta se elige sola.
La disciplina: un único canal de escritura
Aquí está la ley que sostiene todo lo anterior. El proxy que devuelve createStore es de solo lectura. Intentar mutarlo directamente no es un atajo, es un error que Solid rechaza en desarrollo, porque una mutación por fuera del setter no pasa por el registro que convierte cambios en notificaciones: sería un cambio invisible para el grafo.
// PROHIBIDO: mutar el proxy directo. Lanza en desarrollo, no notificaestado.usuario.nombre = "Grace";// PROHIBIDO en silencio: mutar el objeto crudo salta la reactividad enteraconst crudo = unwrap(estado);crudo.usuario.nombre = "Grace"; // cambia el dato, nadie se entera// CORRECTO: siempre por el canalsetEstado("usuario", "nombre", "Grace");La trampa más sutil es capturar una referencia interna y mutarla lejos del setter —pasar estado.tareas a una función que hace .sort(), guardar un subobjeto y reasignar sus campos—. Todo eso o bien lanza, o bien, si mediaste un unwrap, muta el dato real sin avisar a nadie, y te deja con una interfaz que discrepa del estado. La regla es absoluta y no admite excepciones «pequeñas»: todo cambio de estado atraviesa setStore, produce, reconcile o modifyMutable; nada se escribe por fuera.