What is Reactivity
Introduction
Reactivity is the foundation for making a Solid app interactive. In this concept article, we'll discuss the basics of reactivity and how it works in Solid.
Reactivity is a programming pattern that lets you set up behavior that "reacts" to data changes automatically.
For example, you might make a shopping cart with a label that displays the total cost of the items. With reactivity, you can tell that label to automatically update whenever an item gets added or removed.
This way, you only have to manage the data (like adding an item to the cart). Once you wire up the UI elements (like the label) to the data, the UI updates (changing the label) are handled for you.
To get started in Solid, you need a basic understanding of reactive signals and effects. In this article, we'll teach you how those work behind the scenes. This deeper understanding of reactivity can help you optimize your code, debug issues, and build your own extensions to the reactive system.
Reactive Primitives
To use reactivity, you create reactive primitives. The most important primitives are signals and effects. In the sections below, we'll explain what signals and effects are, and then implement a simple version of them ourselves.
Other reactive primitives
Solid has other reactive primitive, but they are derived from signals and effects. For example:
- Stores are trees of signals
- Memos are signals that update like effects
- Resources are signals that update when data is fetched from a server
- Render effects are effects that initially run earlier
Signals
A signal represents a piece of data that can change. For example, a signal could be a username or the value of a counter that we can increment. Signals consist of two main parts:
- The getter, a function that lets you access the current value of the signal
- The setter, a function that lets you set the current value of the signal.
We create a signal using Solid's createSignal
function. This returns the getter and the setter as a two-element array. We typically use array destructuring to assign the getter and setter into variables with names of our choosing.
jsx
import { createSignal } from "solid-js";const [count, setCount] = createSignal(1);console.log(count()); // prints "1"setCount(0); // Sets the count value to 0
jsx
import { createSignal } from "solid-js";const [count, setCount] = createSignal(1);console.log(count()); // prints "1"setCount(0); // Sets the count value to 0
In this example, count is the getter and setCount is the setter.
Effects
An effect represents the action we would like to take when the data in one or more signals change. Solid provides a createEffect
function that itself takes in a function. Solid will run that function, and then rerun it whenever any signal inside that function changes value.
For example, the following effect will console.log
the count whenever it updates:
jsx
import { createSignal, createEffect } from "solid-js";const [count, setCount] = createSignal(0);createEffect(() => {console.log(count());});
jsx
import { createSignal, createEffect } from "solid-js";const [count, setCount] = createSignal(0);createEffect(() => {console.log(count());});
Since you're coming from React
Notice that you don't have to specify a dependency array for an effect in Solid. This is a major difference between Solid and React—Solid will automatically track dependencies whereas React will not. Therefore, React re-runs any effect when a component re-renders unless the developer explicitly specifies dependencies.
Creating a reactive system
One of the best ways to understand how reactivity works is to implement it ourselves. In this section, we will implement the same reactive pattern Solid uses: the observer pattern. In the observer pattern, data (signals) maintain a list of their subscribers (effects). When its data changes, a signal triggers all of its subscribers.
Let's use the same names, createSignal
and createEffect
, for our implementation:
jsx
function createSignal() {}function createEffect() {}const [count, setCount] = createSignal(0);createEffect(() => {console.log("The count is " + count());});
jsx
function createSignal() {}function createEffect() {}const [count, setCount] = createSignal(0);createEffect(() => {console.log("The count is " + count());});
First, let's handle the basics of the createSignal
function. It needs to:
- initialize the
count
value to0
(the argument provided tocreateSignal
) - return a two-element array consisting of a getter and a setter function
jsx
function createSignal(initialValue) {let value = initialValue;function getter() {return value;}function setter(newValue) {value = newValue;}return [getter, setter];}
jsx
function createSignal(initialValue) {let value = initialValue;function getter() {return value;}function setter(newValue) {value = newValue;}return [getter, setter];}
We can now get the current value of our signal by calling the getter and we can set the value by using the setter. This is great, but there is no reactivity yet.
Next, let's set up createEffect
. We know it takes a function and runs it:
jsx
function createEffect(fn) {fn();}
jsx
function createEffect(fn) {fn();}
The key to reactivity is establishing the relationship between createSignal
and createEffect
.
To do this, we give each signal a subscriber list. When we give createEffect
a function and run it, we want a way to tell any signals that are called along the way to add that function to their subscriber list. Our next steps:
- Create a global
currentSubscriber
that can keep track of the function we pass tocreateEffect
- Register the function we pass to
createEffect
as the current subscriber - When we access a signal, add the current listener to a list of subscribers
- When we set the signal to a new value, run all subscribers
jsx
let currentSubscriber = null;function createSignal(initialValue) {let value = initialValue;// Maintain a list of a signal's own subscribersconst subscribers = new Set();function getter() {// Add to subscriber listif (currentSubscriber) {subscribers.add(currentSubscriber);}return value;}function setter(newValue) {value = newValue;// Notify all subscribers of the value changefor (const subscriber of subscribers) {subscriber();}}return [getter, setter];}function createEffect(fn) {// Add function as subscriber in global scopecurrentSubscriber = fn;// Run function to trigger the accessor of any signals that are calledfn();// Remove the function as the current subscribercurrentSubscriber = null;}
jsx
let currentSubscriber = null;function createSignal(initialValue) {let value = initialValue;// Maintain a list of a signal's own subscribersconst subscribers = new Set();function getter() {// Add to subscriber listif (currentSubscriber) {subscribers.add(currentSubscriber);}return value;}function setter(newValue) {value = newValue;// Notify all subscribers of the value changefor (const subscriber of subscribers) {subscriber();}}return [getter, setter];}function createEffect(fn) {// Add function as subscriber in global scopecurrentSubscriber = fn;// Run function to trigger the accessor of any signals that are calledfn();// Remove the function as the current subscribercurrentSubscriber = null;}
This is all the code needed to create a basic reactive system. We can demonstrate that it works by incrementing the count
value every second and watching the console.
jsx
const [count, setCount] = createSignal(0);createEffect(() => {console.log("The count is " + count());});setInterval(() => {setCount(count() + 1);}, 1000);
jsx
const [count, setCount] = createSignal(0);createEffect(() => {console.log("The count is " + count());});setInterval(() => {setCount(count() + 1);}, 1000);

We have just implemented the most basic form of Solid's reactivity system!
Effect tracking is synchronous
One observation we should note about our reactivity system is that it's synchronous. It registers the subscriber globally, runs the effect, and unregisters the subscriber.
So what happens if our createEffect
function looks like this?
jsx
createEffect(() => {setTimeout(() => {console.log(count());}, 1000);});
jsx
createEffect(() => {setTimeout(() => {console.log(count());}, 1000);});
Our createEffect
implementation doesn't wait around for this setTimeout
callback to execute, so by the time we call our count
getter, there is no subscriber in the global scope. The count
signal won't register this callback as one of its subscribers.
Handling asynchronous effects
Solid gives you some options for handling asynchronous effects. For example, you can use the on
function to manually specify effect dependencies.
To learn more about Solid-specific tracking mechanisms, see the Tracking Concept documentation.
Learning more
We hope you enjoyed this introduction to reactivity! If you would like to dive deeper, please check out the following resources: