TypeOfNaN

What does it mean that Solid.js is truly reactive?

Nick Scialli
May 18, 2022

New — Check out my free newsletter on how the web works!

A question I have been hearing a lot recently is about Solid’s reactivity. Specifically, what makes a framework like Solid more reactive than, well, React.

The answer is the pub/sub model employed by Solid’s signals. Using a pub/sub model, we’re able to write the following code:

const [count, setCount] = createSignal(0);

setTimeout(() => {
  setCount(count() + 1);
}, 1000);

createEffect(() => {
  console.log(count());
});

And we will log the count every time it’s updated (every second). You’ll notice that we don’t have to enumerate our effect’s dependencies—it automatically knows that it should be triggered when count is updated.

How it works

To help understand how reactivity works, we can actually build our own createSignal and createEffect primitives. This will both give us some insight into reactivity and “pull back the curtain” on some perceived framework magic.

To start out with, let’s make createSignal. Based on how we use it, we know it will take an initial value as an argument and return a two-element tuple—a getter and a setter:

function createSignal(initialValue) {
  let value = initialValue;

  function getter() {
    return value;
  }

  function setter(newValue) {
    value = newValue;
  }

  return [getter, setter];
}

To reiterate, we have a createSignal function that takes an initialValue for our signal. It returns a two-element array: the first element is getter, which gets the value, and the second element is setter, which sets the value to newValue.

Next, we need to set up our effect. The trick with effects in this pub/sub model is that createEffect will put the function it calls in the global scope so that the signal can capture it as a listener. That’s a lot to grok, so let me show you what this looks like:

let listener;

function createSignal(initialValue) {
  let value = initialValue;
  const listeners = [];

  function getter() {
    if (listener) {
      listeners.push(listener);
    }

    return value;
  }

  function setter(newValue) {
    value = newValue;
  }

  return [getter, setter];
}

function createEffect(func) {
  listener = func;
  func();
  listener = undefined;
}

So let’s think about what happens when we call the following:

createEffect(() => {
  console.log(count());
});
  1. We have a global listener variable that gets assigned the entire function inside createEffect
  2. That function is actually executed
  3. When that function is executed, the getter for count is called, which adds the listener to the signal’s local listeners array
  4. We set the global listener back to undefined

After our createEffect has been called, any signal called during the initial execution will have captured the effect function as a listener!

The final step is that each signal needs to trigger all of its listeners whenever its value changes. The can be done by looping over the listeners array in the setter function:

let listener;

function createSignal(initialValue) {
  let value = initialValue;
  const listeners = [];

  function getter() {
    if (listener) {
      listeners.push(listener);
    }

    return value;
  }

  function setter(newValue) {
    value = newValue;

    listeners.forEach((listener) => {
      listener();
    });
  }

  return [getter, setter];
}

function createEffect(func) {
  listener = func;
  func();
  listener = undefined;
}

And that makes for a quick and dirty reactive model! We can now see that the initial code we wrote logs the value of count whenever it changes (every second).

const [count, setCount] = createSignal(0);

setTimeout(() => {
  setCount(count() + 1);
}, 1000);

createEffect(() => {
  console.log(count());
});

count being logged every second

🎓 Learn how the web works

One of the best ways to level up your tech career is to have a great foundational understanding of how the web works. In my free newsletter, How the Web Works, I provide simple, bite-sized explanations for various web topics that can help you boost your knowledge. Join 2,500+ other learners on the newsletter today!

Signing up is free, I never spam, and you can unsubscribe any time. You won't regret it!

Sign up for the newsletter »
Nick Scialli

Nick Scialli is a senior UI engineer at Microsoft.

© 2024 Nick Scialli