My journey to reactive signals

Andrea Giammarchi
3 min readSep 19, 2022

--

When all my tweets ditched hooks in favor of signals, my curiosity couldn’t stop me from making a tiny (enough) library around this pattern.

Photo by Carlos Alberto Gómez Iñiguez on Unsplash

What are signals?

Somehow a simplification of, or a second take at, React hooks.

The best documentation around signals, beside being the source also pioneer (update: actually S came first!) of such pattern, can be found in solid-js API page.

For lazier reader though, or those not clicking around too much (pun intended within the coming example), signals are a way to “signal” any (transparently) subscribed listener, that some value, or state, has changed:

const clicks = signal(0);
effect(() => {
console.log(`So far, ${clicks} clicks`);
});
counter.addEventListener('click', () => {
clicks.value++;
});

You can try on CodePen this basic example to read logs that will go from 0 to X, as much as you’d like to play with it.

What’s going on … ?

In few words, a signal is a value wrapped into some reactive primitive that is able to subscribe updates to any callback reaching it out. In this previous example, a signal also exposes a toString method that would implicitly return its value, making it one of those accessed signals, hence able to subscribe updates, but in short, whenever such signal value changes, anything that should side-effect to such change will be invoked to perform a new task based on the assumption the outcome will change, as the console.log suggests.

Meet computed: aka memoized callback

While signals are awesome at storing, and representing, any value, these are not great at computing such values.

For this task though, there is a helper that would make things much easier to reason about: computed signals!

// transform input value into a number
const asNumber = input => parseFloat(input.value);
// involved signals bound to their inputs
const a = signal(asNumber(first) || 0);
const b = signal(asNumber(second) || 0);
// return the sum of all involved signals
const summed = computed(() => {
const result = a + b;
console.log(`the new result is ${result}`);
return result;
});
// update first value as number
first.addEventListener('input', () => {
a.value = asNumber(first);
});
// update second value as numer
second.addEventListener('input', () => {
b.value = asNumber(second);
});
// on click, shows the sum of previous values
sum.addEventListener('click', () => {
total.textContent = summed;
});

Feel free to play around live with this example too.

What’s going on … again?

Signals here represent their related input value, and the computed just returns their sum, based on the fact signals expose a valueOf method that returns their value right away, hence it implicitly subscribe listeners which are, in this case, the computed callback.

The total.textContent = summed part of the equation, is solved by the fact computed are still signals, but these are read-only, and yet the implicit toString returns their latest value.

Once such computed is triggered, if any of its signals changed values, it recalculates its value and return it implicitly, without executing more than it’s needed, while both first and second inputs updates.

Wait a minute …

As of today, @preact/signals-core doesn’t support valueOf and solid-js doesn’t support “computed filtering” (as in: it invokes any computed each time for no reason, like in the sum case where either fields can be updated without needing a result in between updates) but that’s mostly the whole point of me writing this post: usignal enables both the great preact DX, while keeping solid-js extra features around!

I’ve iterated over and over on this topic for days now, and I think you’ll easily find the best from both worlds in usignal.

If not thought, please do file a bug and I’ll do my best to address it timely!

As summary, I’ve learned a lot from both projects and hopefully I did make a change to improve them all, so I hope you’ll find a piece of mind while using my alternative to signals, hoping all APIs will somehow converge at some point to simplify developers exploring choice.

Thank You ♥️

--

--

Andrea Giammarchi

Web, Mobile, IoT, and all JS things since 00's. Formerly JS engineer at @nokia, @facebook, @twitter.