Optimizing DOM Updates in SolidJS: Implementing Fine-Grained Reactivity
Learn how to implement fine-grained reactivity in SolidJS using Signals and Stores to eliminate VDOM overhead and prevent unnecessary component re-renders.
29 Mar 2026, 22:39 UTC

The Problem: Unnecessary Component Re-renders
In many modern frameworks, updating a single piece of data triggers a re-evaluation of the entire component function and a diffing process of a Virtual DOM (VDOM). As applications grow, this diffing overhead can lead to performance bottlenecks, especially in data-heavy dashboards or complex forms.
The takeaway: SolidJS eliminates the VDOM entirely. By using Signals, the framework creates a direct link between a piece of state and the specific DOM node that displays it. When the state changes, only that node updates, leaving the rest of the component untouched.
Prerequisites
- Node.js installed (LTS recommended).
- A basic SolidJS project initialized via
npm create viteordegit. - Basic familiarity with JSX syntax.
Implementing Fine-Grained State with Signals
To achieve fine-grained updates, you must use createSignal. Unlike useState in React, a Signal returns a getter function and a setter function. The getter is critical because it tells SolidJS exactly where the value is being used.
import { createSignal, createEffect, createMemo } from "solid-js";
function Counter() {
// count is a getter function, setCount is the updater
const [count, setCount] = createSignal(0);
// Derived state: only recalculates when count changes
const doubleCount = createMemo(() => count() * 2);
return (
<div>
<p>Count: {count()}</p>
<p>Double: {doubleCount()}</p>
<button onClick={() => setCount(count() + 1)}>Increment</button>
</div>
);
}
Handling Complex State with Stores
While Signals are ideal for primitives, using dozens of individual signals for a large object is cumbersome. Stores provide a proxy-based system for deeply nested reactivity. Unlike Signals, Stores allow you to update a specific property of an object without replacing the entire object.
import { createStore } from "solid-js/store";
const [user, setUser] = createStore({
name: "Dev",
settings: {
theme: "dark",
notifications: true
}
});
// Update only the theme property without triggering updates for 'name'
setUser("settings", "theme", "light");
Critical Engineering Constraints
To maintain reactivity, you must avoid patterns that "unwrap" the signal value too early. If you access a signal's value outside of a reactive context (like a JSX expression or an Effect), it becomes a static value.
The Destructuring Trap
One of the most common errors in SolidJS is destructuring props. Because props are proxies, destructuring them converts the reactive reference into a static variable.
| Pattern | Result | Behavior |
|---|---|---|
const { name } = props; |
Broken | Value is captured once at initialization; updates are ignored. |
<p>{props.name}</p> |
Reactive | The getter is called within the JSX, creating a subscription. |
Verification and Diagnostics
To verify that your implementation is truly fine-grained and not re-running the entire component function, follow these steps:
- Insert a Log: Place a
console.log("Component Rendered")at the top level of your component function. - Trigger Update: Click the button that updates a signal.
- Check Console: The log should appear only once (during initial mount). If it appears every time the signal changes, you have accidentally triggered a full component re-run.
- Inspect DOM: Open Browser DevTools. Observe that only the specific text node associated with the signal flashes/updates, rather than the entire parent container.
Rollback and State Recovery
Since SolidJS modifies the DOM directly, there is no "virtual state" to roll back to. To implement undo/redo or state recovery, you must maintain a history stack of your signal values:
const [history, setHistory] = createSignal([]);
const [current, setCurrent] = createSignal(0);
const updateValue = (newVal) => {
setHistory([...history(), current()]);
setCurrent(newVal);
};
const undo = () => {
const prev = history().pop();
if (prev !== undefined) setCurrent(prev);
};
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.