Fine-Grained Reactivity in SolidJS: Updating Only What Changes
Learn how SolidJS signals update only the changed DOM nodes, avoiding virtual‑DOM overhead, and see a concrete counter example plus trade‑offs.
20 Jul 2026, 12:43 UTC

The problem: unnecessary work in UI updates
When a piece of state changes in many UI libraries, the framework often walks a virtual representation of the whole component tree, creates a new diff, and then applies patches to the DOM. Even if only a single text node should change, the framework may still allocate objects, run reconciliation logic, and touch sibling elements that never actually differ. This extra work adds CPU overhead and can cause layout thrash in complex pages.
Thesis: SolidJS signals let the UI update exactly the nodes that depend on the changed value
SolidJS treats reactive values as primitive Signal objects. A signal holds a value and maintains a list of subscribers. When the signal’s value changes, it notifies only those subscribers, and the compiler has already generated fine‑grained updaters that touch the precise DOM nodes or effects that subscribed. There is no virtual DOM reconciliation pass.
How a signal works
createSignalreturns a getter and a setter.- Any code that calls the getter inside a tracking scope (e.g., JSX,
createEffect,createMemo) becomes a subscriber. - When the setter runs, the signal notifies its subscribers synchronously.
- The generated updater mutates only the bound DOM node or re‑runs the subscribed effect.
Worked example: a counter that updates only its text node
Below is a minimal SolidJS component. Clicking the button increments the count, and only the text node displaying the number changes.
import { createSignal } from 'solid-js'
import { render } from 'solid-js/web'
function Counter() {
const [count, setCount] = createSignal(0)
return (
setCount(count() + 1)}>+1
Count: {count()}
)
}
render(() => , document.getElementById('app'))
To verify that only the text node updates, you can add an effect that logs whenever the count changes:
import { createEffect } from 'solid-js'
createEffect(() => {
console.log('Count changed to', count())
})
Open the browser console, click the button, and you will see a single log line per click. No other component lifecycle methods or effects fire. If you inspect the DOM with the SolidJS devtools, you’ll see the signal has exactly one subscriber: the <p> element’s text node.
Trade‑off and limitation
While fine‑grained reactivity eliminates unnecessary reconciliation, it introduces two practical concerns:
- Deep dependency chains – If a signal is read deep inside a nested component tree, each intermediate reading creates a subscription. Very deep chains can increase memory usage and make tracing harder. Keeping signals close to where they are used, or extracting derived values with
createMemo, helps bound the chain length. - Non‑serializable state – Signals are objects with hidden subscription logic;
JSON.stringifywill not preserve their reactive nature. To persist state, you must extract the plain value (e.g.,count()) before serialization and re‑initialize a signal on load.
You can check for overly deep chains by enabling the SolidJS devtools’ “Signal Graph” view and looking for long paths from a root signal to leaf subscribers. If you notice many intermediate nodes, consider flattening the state or moving shared logic into a memo.
Actionable closing
Start by identifying UI parts that update frequently but only affect a small slice of the DOM (form inputs, counters, toggles). Replace any ad‑hoc state handling with createSignal and let the compiler generate the updaters. For lists, use splitFor to create per‑item signals rather than mapping over an array and recreating nodes on every change. Finally, validate your assumptions with the devtools: verify that each signal’s subscriber set matches the UI you expect to change, and that no extra effects fire on unrelated updates.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.