SolidJS Signals: Update Only the DOM That Changes
Use createSignal to update only the dependent DOM nodes instead of re-running the whole component.
31 May 2026, 02:58 UTC

A state change that re-renders more than it should
In many UI frameworks, changing a piece of state causes the component function to run again. The framework then diffs the resulting virtual tree and patches the DOM. Even when only one number changes, the component body re-executes and unrelated markup can be revisited.
SolidJS takes a different approach: a component function runs once to build the DOM, and after that, only the specific DOM nodes tied to a changed signal are updated. This is useful when a page has a frequently updated counter, timer, or form field sitting next to static or slowly changing content.
What createSignal gives you
A signal is a plain JavaScript object that holds a value and notifies a reactive graph when that value changes. createSignal returns a getter and a setter. Reading the getter inside JSX registers that DOM node as a dependent. Writing through the setter updates only those dependents.
This is called fine-grained reactivity: the framework tracks individual reads rather than re-rendering whole components. There is no virtual DOM diffing pass for every state change.
Worked example: a counter that updates one text node
Create a new project in your terminal. Normal user permissions are sufficient:
npm create vite@latest solid-signal-demo -- --template solid
cd solid-signal-demo
npm install
npm run dev
Expected result: Vite starts a development server, typically at http://localhost:5173. Risk: if that port is already in use, Vite will prompt to run on another port.
Replace the default App.jsx with this component:
import { createSignal } from 'solid-js';
import { render } from 'solid-js/web';
function App() {
// The signal API stabilized in SolidJS 1.0 and is unchanged in later minor releases.
const [count, setCount] = createSignal(0);
return (
Count: {count()}
setCount((c) => c + 1)}>Increment
);
}
render(App, document.getElementById('app'));
count() is the getter. Because it is read while the <b> element is being created, that text node subscribes to the signal. Clicking the button calls the setter, and only the <b> text node changes. The App function does not run again.
To verify, open browser developer tools, inspect the <b> element, click Increment, and confirm that only that node's text changes. You can also add console.log('render') at the top of App; it should print once on load and not again after clicks.
Limitation: direct DOM mutations are invisible to signals
Signals track reads and writes through their API. If you mutate the DOM directly—for example, element.textContent = 'new'—the signal does not know about it, and the UI can fall out of sync with the reactive graph. Always use the setter or a derived signal such as createMemo for values that depend on other signals.
Another design decision is what to pass to child components. Passing count() sends a plain number; the child cannot react to later changes. Pass the getter count itself if the child needs to read the current value reactively.
Practical check before shipping
- Run
npm run devand open the page. - Trigger the state change, for example by clicking Increment.
- Use the browser inspector to confirm only the dependent DOM node updates.
- Confirm the component function does not re-execute by checking for a one-time console log.
If more than the expected node changes, the state may be stored outside a signal or passed as a static value rather than a getter.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.