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.
Has aprendido cuatro formas de escribir un store: la ruta puntual, el actualizador que deriva, el produce que muta un borrador y el vocabulario de selección para arrays. Faltan dos piezas y una ley. Las piezas son reconcile, que fusiona un árbol externo conservando la identidad de lo que no cambió, y modifyMutable, que aplica un modificador sobre la raíz entera en un solo lote. La ley, la que da sentido a todo el nivel, es que el store tiene un único canal de escritura: fuera de él, cualquier mutación es clandestina, y lo clandestino es, para el grafo reactivo, inexistente.
- Usar
reconcilepara fusionar datos externos preservando identidad de filas. - Aplicar
modifyMutablepara reconciliar o producir sobre la raíz del store. - Combinar ruta, actualizador,
produceyreconcileen un flujo coherente. - Interiorizar que el proxy es de solo lectura y nunca se muta fuera del setter.
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.
Recuerda la frontera de la lección 3: produce edita, reconcile fusiona lo externo. Volcar una respuesta del servidor con produce o con un reemplazo por ruta te obliga a razonar a mano qué cambió; reconcile lo calcula por ti y minimiza la escritura. Reserva reconcile para el momento en que un árbol de fuera entra al store, que es exactamente donde la identidad importa y donde el reemplazo ingenuo duele.
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 lote
modifyMutable(estado, reconcile(desdeServidor, { key: "id" }));
// Agrupar varias mutaciones del mutable en una unica propagacion
modifyMutable(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 setter
setEstado(reconcile(estadoCompletoDelServidor, { key: "id" }));
// createMutable: el mismo efecto, via modifyMutable porque no hay setter
modifyMutable(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.
flowchart TD
A[que necesito escribir] --> B{forma del cambio}
B -->|una hoja puntual| C[setStore ruta valor]
B -->|derivar del previo| D[setStore ruta actualizador]
B -->|varios campos y logica| E[setStore produce]
B -->|datos externos entrando| F[setStore reconcile]
B -->|raiz de un store mutable| G[modifyMutable con reconcile o produce]
style C fill:#89b4fa,color:#11111b
style D fill:#89b4fa,color:#11111b
style E fill:#cba6f7,color:#11111b
style F fill:#a6e3a1,color:#11111b
style G fill:#fab387,color:#11111breconcile empareja el árbol viejo con el nuevo por la propiedad que indica key, "id" por defecto. Ajústala al identificador estable de tus datos —"uuid", "slug", lo que uses— para que dos representaciones del mismo elemento se reconozcan pese a ser referencias distintas. Esa clave es lo que permite que reordenar la lista en el servidor no remonte una sola fila en el cliente: solo se reposicionan nodos que ya existían, con sus efectos y su estado local intactos.
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 identidad
setEstado("tareas", reconcile(await cargar(), { key: "id" }));
// Edicion compuesta local: produce
setEstado(produce((s) => {
s.filtro = "pendientes";
s.tareas.forEach((t) => { if (t.vencida) t.prioridad = "alta"; });
}));
// Toque puntual: ruta
setEstado("tareas", (t) => t.id === 7, "hecha", true);
// Derivar del previo: actualizador
setEstado("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 notifica
estado.usuario.nombre = "Grace";
// PROHIBIDO en silencio: mutar el objeto crudo salta la reactividad entera
const crudo = unwrap(estado);
crudo.usuario.nombre = "Grace"; // cambia el dato, nadie se entera
// CORRECTO: siempre por el canal
setEstado("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.
createMutable sí permite la mutación directa: ahí estado.x = 1 es la API, no un bug. Pero esa comodidad tiene un precio que debes elegir conscientemente: las escrituras dejan de estar centralizadas en un setter y pueden ocurrir en cualquier parte, lo que dispersa el rastro de «quién cambió qué» y complica el razonamiento en bases grandes. Úsalo para interoperar con código imperativo o para prototipos, no como norma. En la duda, createStore y su canal único envejecen mejor.
Todo este nivel puede resumirse en una sola idea, y esta es: en un sistema de grano fino, el cambio no existe hasta que el sistema lo registra, y el registro solo ocurre si el cambio entra por el canal que sabe interpretarlo. Un store no es un objeto que Solid observe mágicamente desde fuera vigilando cada byte; es un proxy que convierte escrituras declaradas a través de su setter en notificaciones dirigidas a los lectores exactos de cada hoja. Esa conversión es el corazón de la reactividad, y tiene una puerta de entrada única. Cuando escribes por una ruta, el proxy sabe qué hoja tocaste y a quién avisar. Cuando usas produce, el borrador registra cada mutación y la traduce a esas mismas notificaciones. Cuando aplicas reconcile, el modificador calcula el diferencial y escribe solo lo distinto, preservando la identidad de lo igual para que las listas no remonten. Cuando usas modifyMutable, envuelves un modificador sobre la raíz en un lote. Todas son variaciones de un mismo acto: declararle al sistema, en un lenguaje que entiende, qué ha cambiado. Mutar el proxy por fuera, o peor, mutar el objeto crudo tras un unwrap, rompe el contrato en su raíz: cambias el dato pero no pasas por el traductor, de modo que el grafo sigue creyendo que el mundo es el de antes, y la interfaz se congela sobre una realidad que ya no existe. No hay error más desmoralizante ni más fácil de evitar, y por eso la madurez con stores no se mide por conocer las cinco herramientas —eso es vocabulario— sino por haber interiorizado que comparten una sola puerta. La reactividad de grano fino te regala una precisión quirúrgica: solo despierta quien mira la hoja que tocaste, ni un lector de más ni de menos. Ese regalo tiene una única condición, y es que respetes la puerta. Cúmplela sin excepciones y el store se vuelve el estado más predecible que manejarás: cada cambio visible, cada notificación mínima, cada fila estable salvo cuando de verdad cambia. Rómpela una vez «por comodidad» y habrás reintroducido, en el sistema diseñado para eliminarlo, el bug que este nivel entero existe para erradicar.
- Reconcilia una lista con datos simulados del servidor usando
key: "id"y confirma en devtools que las filas sin cambios no remontan. - Compara reconciliar con reemplazar el array entero y observa la diferencia en identidad de nodos y disparo de efectos.
- Usa
createMutableconmodifyMutableyproducepara agrupar tres mutaciones en una sola propagación. - Provoca el bug: muta el proxy de un
createStoredirectamente y verifica que lanza; luego múltalo trasunwrapy comprueba que nadie reacciona. - Escribe un flujo que ingiera con
reconcile, edite conproduce, toque un campo por ruta y derive con un actualizador; justifica la herramienta elegida en cada paso.