Diagnosing SolidJS Store Reactivity Failures: Why Your UI Stays Static After Mutations
A diagnostic guide for SolidJS store reactivity failures. Learn why UI stays static after mutations, how to check proxy chains, and when to escalate SSR or renderer issues.
05 Sept 2026, 08:25 UTC

Recognizable Condition
Your SolidJS component renders correctly the first time, but after calling a store mutator—such as setStore, a produce callback, or a nested setter—the DOM does not update. In some cases the change appears only after an unrelated re-render. In others it never appears at all. This pattern usually means the reactivity graph lost the connection between the store mutation and the component's tracking scope.
Common Causes and Diagnostic Signals
| Condition | Likely Cause | Diagnostic Signal |
|---|---|---|
UI static after calling setStore | Direct assignment outside a setter or produce wrapper | Devtools log shows the mutation, but createEffect does not fire |
| Nested property updates seem lost | Object spread or plain assignment replaces the proxy-wrapped object | Nested reference changes; parent store shape is altered |
| Values are stale inside JSX but correct in effects | Destructuring the store at render time | Effect reads fresh data; template reads a captured snapshot |
| Store works in dev but not after SSR hydration | Server and client created different store instances | Hydration warning; server and client store IDs differ |
| Devtools show updates but UI stays static | Missing devtools enablement or a custom renderer that skips reconciliation | Devtools timeline is active; no DOM patch occurs |
Ordered Diagnostic Checks
- Confirm the mutation path. Open the file where the store changes. If you see
store.user.name = "x"orstore.count++outside a setter function, that is the problem. Solid stores are built on JavaScriptProxyobjects; direct assignment mutates the underlying target but does not notify the reactive graph. - Check for destructuring in render scope. Look for
const { name } = store.user;inside a component body. Destructuring reads the current value once and returns a plain primitive or object reference. It does not preserve the property access that Solid's compiler needs to wire up dependencies. - Inspect nested object replacement. Search for patterns like
setStore("user", { ...store.user, age: 31 }). Spreading creates a new plain object. Unless that object is re-wrapped by the store's internal proxy machinery, nested reactivity is severed. - For SSR, verify store lifecycle. Confirm that the store is created inside the request handler, not at module scope. A module-level store is shared across requests and produces a different instance on the client during hydration.
- Validate the renderer and devtools. If you use a custom renderer, ensure it invokes the reconciliation loop that Solid provides. If devtools are enabled but the UI does not move, the renderer is likely not receiving the update signal.
Fixes Tied to Findings
Replace direct assignment with setters or produce
Use the setter form returned by createStore for shallow changes:
import { createStore } from "solid-js/store";
const [store, setStore] = createStore({ count: 0 });
// Correct: setter notifies dependencies
setStore("count", (c) => c + 1);
For deep or multi-property updates, use produce:
import { produce } from "solid-js/store";
setStore(
produce((state) => {
state.user.name = "Ada";
state.user.age = 36;
})
);
produce gives you a mutable draft. Solid tracks the changes you make and applies them immutably under the hood, preserving the proxy chain.
Stop destructuring stores in render scope
Access properties through the store proxy so the compiler can track them:
// Avoid
const { name } = store.user;
return <h1>{name}</h1>;
// Prefer
return <h1>{store.user.name}</h1>;
If you need to pass props to a child, use splitProps or pass the store itself. Passing the store preserves reactivity; passing a destructured value does not.
Use reconcile for nested object replacement
When you must replace a nested object—for example after a fetch—use reconcile so Solid can diff the new value against the existing proxy:
import { reconcile } from "solid-js/store";
setStore("user", reconcile(fetchedUser));
reconcile compares keys and updates only what changed. Plain spread replacement breaks the link because the new object is not instrumented by the store's proxy.
Recreate the store per SSR request
Move store creation into the request context. A common pattern is a factory:
// server entry (pseudo-code)
app.get("*", (req, res) => {
const [store, setStore] = createStore(initialData(req));
res.send(renderToString(() => <App store={store} />));
});
On the client, recreate the store from the serialized payload before hydration. If the server and client stores have different identities or shapes, hydration mismatches will drop subscriptions.
Enable devtools and verify renderer integration
Enable Solid devtools early in your client entry. If you use a custom renderer, confirm it invokes the reconciliation function that Solid provides. Without that call, the reactive graph updates but no DOM patch is scheduled.
Practical Verification
Add a temporary effect next to the component you suspect:
import { createEffect } from "solid-js";
createEffect(() => {
console.log("user name is now:", store.user.name);
});
Trigger the mutation. If the log fires but the DOM does not change, the problem is in rendering or destructuring. If the log does not fire, the mutation bypassed the reactive graph—usually direct assignment or a broken proxy chain.
For SSR, compare the serialized store payload with the client store after hydration. They should have the same shape and initial values. A mismatch means the server and client are not sharing the same reactive contract.
Escalation Criteria
Escalate to a deeper review if:
- The effect fires and the DOM updates, but only after a second interaction—suggesting a batching or scheduler issue outside the store.
- You are using a third-party state library bridged into Solid and the bridge itself drops the proxy.
- SSR hydration succeeds but updates are lost only in production builds, which may indicate a minification or tree-shaking problem with the store module.
Limitations
This guide covers the standard createStore API from solid-js/store. Custom store implementations, class-based reactivity, or non-standard compilers may behave differently. Always verify against the SolidJS version documented for your project; store internals and devtools integration have evolved across releases.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.