Stop Re-rendering: Mastering Fine-Grained Reactivity in SolidJS
Learn how SolidJS eliminates the Virtual DOM using fine-grained reactivity. Explore the practical use of Signals and Memos to build high-performance UIs without unnecessary re-renders.
22 Apr 2026, 21:04 UTC

The Cost of the Virtual DOM
In many modern UI frameworks, updating a single piece of text often requires the framework to re-run the entire component function, generate a new Virtual DOM tree, and "diff" it against the previous version to find what changed. As applications grow, this diffing process becomes a performance tax, even if the actual DOM change is trivial.
SolidJS solves this by removing the Virtual DOM entirely. Instead of re-running components, it uses fine-grained reactivity. The takeaway is simple: in SolidJS, your component functions are setup scripts that run exactly once. The reactivity lives in the specific expressions you wrap in signals, which link directly to the DOM nodes they control.
Signals: The Atomic Units of State
At the core of this system is the createSignal primitive. A signal returns a getter and a setter. Unlike state in other frameworks, the getter is a function. This is a critical engineering decision: by calling a function, SolidJS can track exactly who is "listening" to that piece of state at the moment the code executes.
When a signal's value changes, SolidJS doesn't tell the component to re-render. It looks at its internal subscription graph and triggers only the specific observers—such as a text node update or an attribute change—that called that getter.
Optimizing Derived State with Memos
While signals handle raw state, createMemo handles derived state. A memo is essentially a cached value that only recalculates when its dependencies change. If you have a complex calculation based on multiple signals, wrapping it in a memo prevents that calculation from running every time any part of the UI updates.
However, there is a trade-off. Memos occupy memory to store the cached value and the subscription list. Using a memo for a simple string concatenation is often overkill and can lead to unnecessary memory overhead. Use memos for expensive computations or to prevent "downstream" updates when the result of a calculation hasn't actually changed.
Practical Example: A Reactive Filter
Consider a list of items that needs to be filtered based on a search term. In a VDOM framework, the entire list would likely re-render on every keystroke. In SolidJS, we can isolate the filter logic.
import { createSignal, createMemo } from "solid-js";
function UserList() {
const [searchTerm, setSearchTerm] = createSignal("");
const users = ["Alice", "Bob", "Charlie", "David"];
// This memo only recalculates when searchTerm changes
const filteredUsers = createMemo(() => {
return users.filter(user =>
user.toLowerCase().includes(searchTerm().toLowerCase())
);
});
return (
<div>
<input
type="text"
onInput={(e) => setSearchTerm(e.currentTarget.value)}
placeholder="Search users..."
/>
{/* The For component ensures only changed items are touched in the DOM */}
{(user) => <li>{user}</li>}
</div>
);
}
Implementation Details
- Execution: Run this within a standard SolidJS project (e.g., via Vite).
- Permissions: No special system permissions required; runs in the browser context.
- Expected Behavior: The
UserListfunction runs once. ThefilteredUsersmemo updates only whensearchTermchanges, and theForcomponent updates only the necessarylielements.
The Reactivity Trap: Destructuring Props
The most common mistake for developers moving to SolidJS is destructuring props. Because SolidJS relies on getters to track dependencies, destructuring a prop converts a reactive getter into a static value.
Incorrect:
function Greeting({ name }) {
return <div>Hello {name}</div>;
}
Correct:
function Greeting(props) {
return <div>Hello {props.name}</div>;
}
In the incorrect example, name is accessed once during the setup phase. If the parent component updates the name, the Greeting component will not update because the reactive link was broken during destructuring.
Verifying the Performance Gain
To verify that your components aren't re-rendering unnecessarily, you can use a simple diagnostic check:
- Insert a
console.log("Component Rendered")at the top level of your component function. - Interact with a signal that updates a value inside that component.
- Observe the console. You should see the log only once during the initial mount, regardless of how many times the state changes.
For a deeper dive, use the Browser DevTools Elements tab. When a signal updates, you will see only the specific text node flash (in some browsers) or update, while the surrounding HTML tags remain untouched.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.