Preventing Reactivity Loss in SolidJS: Signals and the Destructuring Trap
Learn why destructuring props breaks reactivity in SolidJS and how to leverage fine-grained signals and stores to ensure efficient, direct DOM updates without a Virtual DOM.
30 Jul 2025, 06:20 UTC

The Core Problem: Why Your UI Isn't Updating
In many JavaScript frameworks, components are functions that re-run entirely when state changes. In SolidJS, the component function runs only once. It sets up a reactive graph of dependencies and then disappears. If you lose the connection to a signal (a reactive state primitive), the DOM node associated with that signal will never update, even if the underlying data changes.
The most common cause of this "frozen UI" is destructuring props. Because SolidJS tracks dependencies through property access (getters), breaking a prop object apart into individual variables strips away the reactivity.
How Fine-Grained Reactivity Works
SolidJS avoids the Virtual DOM (VDOM) by using a compiler that transforms JSX into direct DOM manipulation. Instead of comparing two large object trees to find changes, it creates a subscription: a specific piece of data (the Signal) is linked directly to a specific DOM update function.
- createSignal: Returns a getter and a setter. The getter is a function; calling it tells SolidJS, "Whoever is currently running depends on this value."
- createEffect: A function that runs once and then re-runs whenever any signal accessed inside its body changes.
Worked Example: Reactive vs. Broken Props
Consider a scenario where a parent component passes a user's name to a child component. The following example demonstrates the correct way to maintain reactivity and the common mistake that breaks it.
// ChildComponent.jsx
import { createSignal } from "solid-js";
function UserProfile(props) {
// ❌ WRONG: Destructuring breaks reactivity
// const { name } = props;
// {name} will never update because 'name' is now a static string
// ✅ CORRECT: Access props directly in the JSX
return (
<div>
<p>Current User: {props.name}</p>
</div>
);
}
function App() {
const [name, setName] = createSignal("Alice");
return (
<div>
<button onClick={() => setName("Bob")}>Change Name</button>
<UserProfile name={name()} />
</div>
);
}
Technical Breakdown of the Example
When <UserProfile name={name()} /> is called, SolidJS doesn't just pass the value of name(); it passes a reactive proxy. In the UserProfile component, when the JSX reaches {props.name}, the framework registers a dependency between that specific text node in the DOM and the name signal. When setName("Bob") is called, SolidJS updates only that specific text node, without re-executing the UserProfile function.
Handling Complex State with Stores
Using createSignal for every single field in a large object creates significant memory overhead because each signal requires its own subscription list. For nested data, use createStore, which utilizes JavaScript Proxies to track access at a granular level.
import { createStore } from "solid-js/store";
const [state, setState] = createStore({
user: {
profile: { name: "Alice", age: 30 },
settings: { theme: "dark" }
}
});
// Update only the name without replacing the whole object
setState("user", "profile", "name", "Bob");
Limitations and Common Pitfalls
The Cost of Over-Reactivity
While signals are efficient, creating thousands of them for values that don't trigger UI changes (like internal calculation constants) increases the memory footprint of the dependency graph. Use standard let or const variables for data that is truly static after initialization.
Unbatched Updates in Loops
Updating multiple signals inside a loop can trigger multiple synchronous DOM updates. To prevent this, wrap updates in batch() to ensure the DOM only updates once after all state changes are processed.
Verification and Diagnostics
To verify if your component is reacting correctly, follow these steps:
- DOM Inspection: Open Browser DevTools. Change a signal value. If the entire component's HTML is replaced, you are likely using a VDOM-like pattern or a key that is changing too often. If only a single text node flashes or updates, fine-grained reactivity is working.
- Console Logging: Place a
console.log("Component Rendered")at the top of your component function. If you see this log every time a signal changes, you have a structural error; the component should only log once during the initial mount. - Prop Check: If a value is not updating, search your code for
const { ... } = props. Replace it with directprops.valueaccess.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.