TypeOfNaN

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

Nick Scialli May 18, 2022🚀 4 minute read

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

Did this post help you?

I'd appreciate your feedback so I can make my blog posts more helpful. Did this post help you learn something or fix an issue you were having?

If you'd like to support this blog by buying me a coffee I'd really appreciate it!

Nick Scialli

Nick Scialli is a software engineer at the U.S. Digital Service.