Fine‑grained Reactivity in SolidJS: Using createSignal and createEffect for Predictable State Updates
Learn how SolidJS’s createSignal and createEffect give O(1) updates and automatic dependency tracking, reducing boilerplate and stale‑closure bugs.
18 Aug 2026, 11:37 UTC

Problem: State updates that feel slow or buggy
In many UI libraries, changing state triggers a full render pass or requires you to list dependencies manually. Forgetting a dependency leads to stale values, while over‑listing causes unnecessary work.
Thesis: SolidJS primitives give O(1) updates and automatic tracking
SolidJS’s createSignal returns a getter that lazily tracks subscribers and a setter that notifies only those subscribers. createEffect subscribes to any signal accessed during its run and re‑runs exactly when those signals change, eliminating manual dependency arrays.
How createSignal works
When you call const [count, setCount] = createSignal(0):
- The getter (
count) returns the current value and registers the caller as a subscriber. - The setter (
setCount) updates the internal value and notifies only the registered subscribers. - Because notification is direct, the cost is proportional to the number of subscribers, not the size of the component tree.
How createEffect tracks dependencies
An effect like:
createEffect(() => {
console.log('Count is', count());
});
During execution, accessing count() tells the effect to subscribe to that signal. If count later changes, the effect re‑runs; if the effect never reads a signal, it stays idle.
Worked example: a live counter
Create a fresh SolidJS project:
npm create solid-app@latest my-counter
cd my-counter
npm run dev
Replace src/App.tsx with:
import { createSignal, createEffect } from 'solid-js';
import { render } from 'solid-js/web';
function App() {
const [count, setCount] = createSignal(0);
// Effect runs whenever count changes
createEffect(() => {
console.log('Effect: count =', count());
});
return (
<>
Count: {count()}
setCount(c => c + 1)}>
Increment
);
}
render(() => , document.getElementById('root')!);
Open the browser console. Each click logs a single line; the effect does not run on every render, only when count changes.
Trade‑off: signal scoping
Creating many fine‑grained signals at the top level increases subscription overhead because each signal maintains its own listener list. To keep costs low:
- Scope signals to the smallest component or store that needs them.
- Group related values into an object signal (
createSignal({ a: 1, b: 2 })) when they always change together. - Use stores (
createStore) for deep nested state when appropriate.
You can verify the impact by opening the SolidJS DevTools, selecting the Signals tab, and watching the number of active subscriptions as you add or remove top‑level signals.
Actionable closing
When you need predictable, low‑overhead state updates in SolidJS, reach for createSignal and createEffect first. Keep signals close to where they are used, and inspect the signal graph with DevTools to ensure you aren’t creating unnecessary subscriptions. This approach gives you O(1) updates, eliminates stale‑closure bugs, and scales linearly with the number of actually changed signals.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.