Concepts

Intro to reactivity

Edit this page

Note: While this guide is useful for understanding reactive systems, it does use some Solid-specific terminology.

Reactivity powers the interactivity in Solid applications. This programming paradigm refers to a system's ability to respond to changes in data or state automatically. With Solid, reactivity is the basis of its design, ensuring applications stay up-to-date with their underlying data.


Importance of reactivity

  1. Reactivity keeps the user interface (UI) and state in sync, which reduces the need for manual updates.

  2. Real-time updates create a more responsive and interactive user experience.

function Counter() {
const [count, setCount] = createSignal(0);
const increment = () => setCount((prev) => prev + 1);
return (
<div>
<span>Count: {count()}</span>{" "}
{/* Only `count()` is updated when the button is clicked. */}
<button type="button" onClick={increment}>
Increment
</button>
</div>
);
}

This Counter function sets up a button that, when clicked, calls the increment function to increase the count by one. This updates just the number displayed without refreshing the entire component.


Reactive principles

Signals

Signals serve as core elements in reactive systems, playing an important role in data management and system responsiveness. They are responsible for storing and managing data, as well as triggering updates across the system. This is done through the use of getters and setters.

const [count, setCount] = createSignal(0);
// ^ getter ^ setter
  • Getter: A function responsible for accessing the current value of the signal. You call a getter to access the data stored in a signal within a component.

  • Setter: The function used to modify a signal's value. To trigger reactive updates across an application, you call a setter to update the value of a signal.

console.log(count()); // `count()` is a getter that returns the current value of `count`, which is `0`.
setCount(1); // the setter, `setCount`, queues an update to `count`.
console.log(count()); // still `0` — the update is batched and will flush on the next microtask.

Subscribers

Subscribers are the other core element in reactive systems. They are responsible for tracking changes in signals and updating the system accordingly. They are automated responders that keep the system up-to-date with the latest data changes.

Subscribers work based on two main actions:

  • Observation: At their core, subscribers observe signals. This keeps the subscriber primed to pick up on any changes to the signal they are tracking.
  • Response: When a signal changes, the subscriber is notified. This triggers the subscriber to respond to the change in the signal. This can involve tasks like updating the UI or calling external functions.
function Counter() {
const [count, setCount] = createSignal(0);
const increment = () => setCount((prev) => prev + 1);
createEffect(
() => count(),
(value) => {
console.log(value);
}
);
// the `createEffect` will trigger the console log every time `count` changes.
}

State management

State management is the process of managing the state of an application. This involves storing and updating data, as well as responding to the changes in it.

With Solid, state management is handled through signals and subscribers. Signals are used to store and update data, while subscribers are used to respond to changes in the data.

Tracking changes

Tracking changes involves monitoring any updates to the data and responding accordingly. This is done through the use of subscribers.

When a signal is not accessed within a tracking scope, an update to the signal will not trigger an update. This happens because if a signal is not being tracked, it is not able to notify any subscribers of the change.

const [count, setCount] = createSignal(0);
console.log("Count:", count());
setCount(1);
// Output: Count: 0
// `count` is not being tracked, so the console log will not update when `count` changes.

Initialization, or creation is a one-time event that doesn't cause tracking. To track a signal, it must be accessed within the scope of a subscriber. Reactive primitives, such as memos can be used to create derived values from signals or other memos, and effects to create subscribers that use the reactive graph output once it's settled.

const [count, setCount] = createSignal(0);
createEffect(
() => count(),
(value) => {
console.log("Count:", value);
}
);
setCount(1);
// Output: Count: 0
// Count: 1

Updating the UI

The UI of a Solid application is built using JSX. JSX creates a tracking scope behind the scenes, which allows signals to be tracked within the return statement of a component.

function Counter() {
const [count, setCount] = createSignal(0);
const increment = () => setCount((prev) => prev + 1);
return (
<div>
<span>Count: {count()}</span>{" "}
{/* ✅ will update when `count()` changes. */}
<button type="button" onClick={increment}>
Increment
</button>
</div>
);
}

Components, much like other functions, will only run once. This means that if a signal is accessed outside of the return statement, it will run on initialization, but any updates to the signal will not trigger an update.

function Counter() {
const [count, setCount] = createSignal(0);
const increment = () => setCount((prev) => prev + 1);
console.log("Count:", count()); // ❌ warns in 2.0 - top-level reactive read.
createEffect(
() => count(),
(value) => {
console.log(value); // ✅ will update whenever `count()` changes.
}
);
return (
<div>
<span>Count: {count()}</span>{/* ✅ will update whenever `count()` changes. */}
<button type="button" onClick={increment}>
Increment
</button>
</div>
);
}

To learn more about managing state in Solid, visit the guide on state management.


Batching and update timing

Reactive systems are designed to respond to changes in data. In Solid 2.0, updates use microtask batching by default — when you call a setter, the update is queued and applied on the next microtask. This means multiple setter calls are naturally batched together, and effects only run once all pending updates have been applied.

How batching works

When you call a setter, the new value is queued but not yet visible to reads. All queued updates are flushed together on the next microtask. This prevents unnecessary intermediate re-renders and ensures subscribers always see a consistent state.

const [count, setCount] = createSignal(0);
const doubled = createMemo(() => count() * 2);

In this example, doubled is a derived value using createMemo. Whenever count changes, doubled will automatically recompute to reflect the new value — there is no need to manually write to a second signal.

When you need immediate updates: flush()

In rare cases where you need all pending updates to be applied right away — for example, before measuring the DOM or focusing an element — use flush():

function handleSubmit() {
setSubmitted(true);
flush(); // apply updates now
inputRef.focus(); // DOM is up to date
}

Use flush() sparingly. In most cases, the default microtask batching produces the best behavior.


Key concepts

  • Signals are the core elements of a reactive system. They are responsible for storing and managing data.
  • Signals are both readable and writeable because of getters and setters.
  • In Solid 2.0, setter calls are batched by default — reads return the old value until the microtask flush.
  • Subscribers are automated responders that track changes in signals and update the system accordingly.
  • Signals and subscribers work together to ensure that the system is kept up-to-date with the latest data changes.
  • A reactive system is built on the principles of data-driven reactivity. This means that the system's reactivity is driven by the data it is built on.
  • Reading signals at the top level of a component body (outside a reactive scope) will produce a dev warning. Always access signals inside JSX, createMemo, createEffect, or wrap with untrack.

If you want to dive deeper, visit the guide on fine-grained reactivity.

Report an issue with this page