Optimizing SolidJS State: When to Use Signals vs. Batching
Learn how to manage SolidJS signals and use batching to prevent DOM thrashing and memory leaks in fine-grained reactive applications.
04 Jul 2026, 14:38 UTC

The Cost of Frequent DOM Updates
In many reactive frameworks, updating state triggers a reconciliation process that checks what changed in a virtual tree. SolidJS avoids this by using fine-grained reactivity, where a Signal (a wrapper around a value with a getter and setter) creates a direct link to the specific DOM node that needs to change. While this is highly efficient, updating multiple signals independently in a single logical operation can cause the browser to perform multiple redundant DOM updates in a single tick.
The core engineering challenge in SolidJS isn't preventing component re-renders—since components only execute once—but managing the frequency and grouping of these fine-grained updates to avoid "DOM thrashing."
How Signals Establish Dependencies
A Signal in SolidJS is not just a value; it is a dependency tracker. When you call a signal's getter inside a JSX expression, Solid records that specific DOM element as a subscriber. This is why you must call the signal as a function (e.g., count()) rather than accessing a property.
If you destructure a prop or a signal (e.g., const { value } = props;), you are accessing the value at a single point in time. This breaks the reactive link because the getter is never called again within the reactive context, and the UI will fail to update when the underlying state changes.
Reducing Noise with Batching
When a single user action needs to update three different signals, SolidJS would normally notify every subscriber of those signals immediately. If those signals all affect the same area of the UI, the browser may perform unnecessary work.
The batch() function allows you to group these updates. Solid suppresses notifications to subscribers until the batch function completes, ensuring that the DOM is updated only once for the entire set of changes. This is critical for maintaining 60fps animations or handling complex form state updates.
Implementation: Coordinated State Updates
The following example demonstrates a coordinate system where updating the X and Y positions simultaneously is batched to prevent the UI from rendering an intermediate, "half-updated" state.
import { createSignal, batch } from "solid-js";
function CoordinateTracker() {
const [x, setX] = createSignal(0);
const [y, setY] = createSignal(0);
const moveDiagonal = () => {
// Without batch, the DOM would update twice
batch(() => {
setX(x() + 10);
setY(y() + 10);
});
};
return (
<div>
<p>Position: {x()}, {y()}</p>
<button onClick={moveDiagonal}>Move Diagonal</button>
</div>
);
}
export default CoordinateTracker;
Verification and Risks
To verify that batching is working, you can wrap the signal getters in a custom function that logs to the console. Without batch(), you will see the log trigger for every set call. With batch(), the log will only trigger once after the function closes.
Risk: Over-using batch() for unrelated state changes can lead to unexpected behavior if you rely on the sequential execution of effects. Always group only those signals that are logically part of the same state transition.
Memory Management and Lifecycle
Because every signal subscription is an object stored in memory, long-lived applications must be cautious about where signals are created. While Solid automatically cleans up subscriptions created within a component's JSX or createEffect, subscriptions created manually outside of these contexts can persist.
Use onCleanup() to dispose of intervals, event listeners, or manual subscriptions. Failure to do so can lead to memory leaks, as the reactive system will continue to track dependencies for components that have been unmounted from the DOM.
Engineering Decision Summary
| Scenario | Recommended Approach | Reasoning |
|---|---|---|
| Single value update | Standard Signal | Lowest overhead for simple changes. |
| Multiple related updates | batch() |
Prevents redundant DOM writes in one tick. |
| Passing state to children | Pass the Signal/Prop directly | Avoids breaking reactivity via destructuring. |
| External event listeners | onCleanup() |
Prevents memory leaks from orphaned subscriptions. |
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.