Avoiding the Re-render Trap: Leveraging Fine-Grained Reactivity in SolidJS
Learn how SolidJS uses fine-grained reactivity and signals to eliminate unnecessary component re-renders, improving UI performance through precise DOM updates.
16 Jan 2026, 01:27 UTC

The Cost of the 'Whole-Component' Update
In many modern UI frameworks, updating a single piece of state triggers a re-execution of the entire component function. This often leads to a cycle of optimization: developers wrap functions in hooks to prevent them from being recreated and memoize expensive calculations to stop them from running on every tick. The problem isn't the state change itself, but the scope of the reaction.
SolidJS solves this by decoupling the component function from the rendering process. In Solid, the component function runs exactly once. The reactivity is tied to the specific DOM nodes that depend on the state, not the component boundary. This is known as fine-grained reactivity.
Signals: The Atomic Unit of State
At the core of this approach are Signals. A signal is a getter and a setter pair that tracks who is calling it. When a signal is read inside a JSX expression, Solid creates a subscription between that specific piece of the DOM and the signal.
Because the component function doesn't re-run, you don't need to worry about the "stale closure" problem common in other hooks-based frameworks. The JSX simply contains a live binding to the signal's value.
Optimizing Derived State with Memos
While signals handle raw state, createMemo handles derived state. A memo is a read-only signal that updates only when its dependencies change. This is critical for performance when you have a calculation that is expensive but depends on multiple signals.
Without a memo, a derived calculation placed directly in JSX would re-execute every time any of its dependencies change, even if the final result of the calculation remains the same. A memo acts as a cache, ensuring that downstream observers only react if the output of the calculation actually changes.
Practical Implementation: The Filtered List
Consider a scenario where you have a large list of items and a search filter. You want to avoid re-calculating the filtered list unless the search term or the list itself changes, and you want to ensure the rest of the UI remains untouched during these updates.
import { createSignal, createMemo, For } from "solid-js";
function UserList() {
const [users, setUsers] = createSignal([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]);
const [filter, setFilter] = createSignal("");
// This memo only re-runs when 'users' or 'filter' changes
const filteredUsers = createMemo(() => {
console.log("Calculating filtered list...");
return users().filter(u => u.name.toLowerCase().includes(filter().toLowerCase()));
});
return (
setFilter(e.currentTarget.value)}
placeholder="Filter users..."
/>
{(user) => {user.name}}
);
}
Verification and Behavior
- Run Location: This code runs in the browser via a SolidJS project (e.g., via Vite).
- Expected Result: The "Calculating filtered list..." log will only trigger when the input changes or the user list is modified.
- Diagnostic Check: If you add a
console.log("Component Rendered")at the top of theUserListfunction, you will notice it prints only once, regardless of how many times you type in the filter box.
The Trade-off: Signal Proliferation
The granularity of SolidJS is its greatest strength, but it introduces a different management challenge: signal proliferation. Creating a separate signal for every single primitive value in a complex form can lead to a fragmented state architecture that is difficult to debug.
When state is highly interdependent, it is often more practical to use a Store (via createStore) rather than multiple signals. Stores provide a proxy-based approach to nested data, allowing you to update deep properties without creating dozens of individual signal getters.
Closing Action
To move from a "component-centric" to a "signal-centric" mindset, start by auditing your most expensive calculations. Move them out of the JSX and into createMemo. If you find your component logic becoming cluttered with too many createSignal calls, migrate that specific state slice to a Store to maintain a clean data hierarchy.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.