Fine‑Grained Reactivity in SolidJS: Signals, Selectors, and Efficient State Management
SolidJS’s signals, selectors, and memo primitives let you limit re‑renders to only affected components. Learn how to use them, check the reactive graph, and avoid common pitfalls with a hands‑on example.
23 Apr 2026, 22:08 UTC

Problem: State Updates in Large Web Apps
When an application grows, a single state change can trigger thousands of DOM updates if the framework re‑renders whole trees. This diffing overhead is the source of many performance bottlenecks in traditional virtual‑DOM libraries.
SolidJS’s Core Primitives
SolidJS replaces the virtual‑DOM diff with a fine‑grained reactive system built on four primitives:
- Signals – immutable getter/setter pairs that notify dependents.
- createEffect – runs a function and auto‑tracks any signals read inside.
- createSelector – memoizes a derived value, recomputing only when the selected part of a signal changes.
- createMemo – caches expensive calculations, re‑executing only on dependency change.
Signals: The Single Source of Truth
A signal is declared with createSignal. The returned array contains a getter and a setter. Updating the value via the setter triggers only the downstream effects that read that signal.
import { createSignal } from 'solid-js';
const [count, setCount] = createSignal(0);
Because the system tracks reads, setCount(1) will notify only the effects that called count() during their last run.
Selecting Sub‑State with createSelector
When you have a large nested object, you can avoid recomputing the entire object by selecting a slice:
import { createSignal, createSelector } from 'solid-js';
const [user, setUser] = createSignal({
name: 'Alice',
stats: { score: 42, level: 3 }
});
const userName = createSelector(() => user().name);
Now userName() will update only when user().name changes, not when user().stats changes.
Memoizing Expensive Computations
For derived data that is costly to compute, createMemo caches the result until its dependencies change:
import { createMemo } from 'solid-js';
const expensive = createMemo(() => {
// Heavy calculation
return [...Array(1000)].reduce((sum, _, i) => sum + i, 0);
});
Calling expensive() returns the cached value until any signal used inside the function changes.
Effects: Reacting to State Changes
Effects automatically track signal reads:
import { createEffect } from 'solid-js';
createEffect(() => {
console.log('Count changed to', count());
});
When setCount is called, this effect re‑runs, logging the new value.
Concrete Example: A Counter with Derived State
- Create a signal for the counter.
- Use
createSelectorto expose only the even/odd status. - Log updates via
createEffect. - Wrap everything in
createRootto scope cleanup.
import { createRoot, createSignal, createSelector, createEffect } from 'solid-js';
createRoot(() => {
const [count, setCount] = createSignal(0);
const isEven = createSelector(() => count() % 2 === 0);
createEffect(() => {
console.log('Count:', count(), '| Even:', isEven());
});
// Simulate button click
setTimeout(() => setCount(1), 1000);
setTimeout(() => setCount(2), 2000);
});
How to verify:
- Open the browser console and observe logs only when
countchanges. - Use the Solid DevTools (or a console.log of the reactive graph) to see that
isEvendepends only oncount, not on any other signals.
Trade‑offs and Limitations
- Manual Serialization – Signals are not JSON‑serializable by default. Extract the value with the getter before sending over the network.
- Large Signal Collections – Creating thousands of signals in a loop can inflate the reactive graph. Prefer a single signal with a selector or memo for bulk data.
- Direct Mutation Bypass – Mutating the value returned by a signal’s getter (e.g.,
array.push()) does not trigger an update. Always use the setter or produce a new value. - Prop Drilling – Passing raw signals as props can create deep dependency chains. Use derived values or context to limit propagation.
Actionable Takeaway
To keep your SolidJS app performant:
- Use
createSignalfor every independent piece of state. - Wrap complex objects in a signal and expose slices via
createSelector. - Cache expensive derived values with
createMemo. - Scope signals and effects with
createRootto avoid leaks. - Regularly inspect the reactive graph with DevTools to spot unintended dependencies.
By following these patterns, you’ll harness SolidJS’s fine‑grained reactivity to build fast, scalable applications that update only the parts that truly need it.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.