Fine‑Grained Reactivity in SolidJS: Signals, Effects, and Efficient Counters
SolidJS replaces virtual‑DOM diffing with fine‑grained reactivity. Learn how signals and effects let you write efficient UI code, illustrated with a counter example and trade‑off discussion.
18 Nov 2025, 08:02 UTC

The Problem: Re‑render Overhead in Component‑Based UIs
In most React‑style frameworks, a component re‑renders whenever any part of its state changes. The framework then diffs the entire virtual DOM tree for that component to figure out which nodes actually need updating. While this works, it can become a performance bottleneck when a component contains many independent pieces of state or when state changes are frequent.
SolidJS’s Solution: Signals and Fine‑Grained Re‑activity
SolidJS replaces the virtual DOM diffing model with a lightweight, fine‑grained reactivity system. Instead of re‑rendering an entire component, Solid tracks exactly which signals a component reads and updates only the DOM nodes that depend on those signals. The core primitives are createSignal and createEffect:
createSignalcreates a reactive value that can be read and written.createEffectruns a function immediately and automatically re‑runs it whenever any signal it reads changes.
Because signals are first‑class values, they can be passed around, composed, and reused across components without extra boilerplate.
How It Works Under the Hood
When a component mounts, Solid evaluates the component’s JSX. Any call to signal() inside that evaluation registers a dependency. When signal.set(value) is called, Solid schedules only the effects that depend on that signal to re‑run. The effects then update the DOM directly, bypassing a virtual DOM diff. This means:
- Updates are instantaneous and have no intermediate re‑renders.
- Only the affected nodes are touched, keeping the rest of the UI untouched.
- Signal subscriptions are automatically cleaned up on unmount, preventing memory leaks.
Concrete Example: A Counter Component
Below is a minimal SolidJS counter that demonstrates the reactivity system in action. The example uses createSignal for state and createEffect to log when the counter changes.
import { createSignal, createEffect } from 'solid-js';
function Counter() {
// 1. Create a signal for the count value
const [count, setCount] = createSignal(0);
// 2. Create an effect that runs whenever 'count' changes
createEffect(() => {
console.log('Counter updated:', count());
});
// 3. Render a button that increments the signal
return (
<div>
<h3>Count: {count()}</h3>
<button onClick={() => setCount(count() + 1)}>Increment</button>
</div>
);
}
export default Counter;
**What happens when you click the button?**
- The
setCountcall updates the signal value. - Solid identifies that
countis a dependency of thecreateEffectand the JSX expression{count()}. - Only the
<h3>element that displays the count is re‑rendered. - The effect runs again, logging the new value.
Because the effect runs synchronously, you can rely on the console log to confirm that it fires only when count changes. If you replace createSignal with a plain variable, the UI will no longer update automatically, highlighting the necessity of signals.
Trade‑offs and Limitations
- Many Signals, Many Effects: If dozens of signals all depend on the same underlying value, each change can trigger many effects. Use
createMemoto derive shared values instead of duplicating logic. - Serialization: Signals are not serializable. Persisting state to
localStorageor sending it over the network requires extracting the raw value (e.g.,count()). - Synchronous Effects: Effects run on the same thread as the signal update. Long‑running logic inside an effect can block the UI. Offload heavy work to
setTimeout,requestIdleCallback, or worker threads. - Complex Dependency Chains: Signals that depend on many sources can become hard to reason about. Document or refactor into smaller components when the chain grows.
Practical Checklist for Using Signals
- Wrap stateful values in
createSignalorcreateStorefor complex objects. - Use
createEffectfor side‑effects that should re‑run on data changes. - Prefer
createMemofor derived values that are used in multiple places. - Verify that only the intended DOM nodes update by inspecting the Elements panel in DevTools.
- Test cleanup by unmounting a component and ensuring no lingering logs or timers remain.
Takeaway
SolidJS’s fine‑grained reactivity turns every signal into a lightweight, automatically tracked source of truth. By coupling createSignal with createEffect, you can write UI code that updates instantly, avoids unnecessary re‑renders, and stays maintainable even as the number of stateful values grows. Keep an eye on effect complexity and serialization needs, but otherwise embrace signals as the foundation of efficient SolidJS applications.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.