Efficient reactivity for modern web development with Signals

In this article, we offer you a brief introduction introduction to the topic of signals and their importance for web development. Discover how you can use reactive data to make your applications more efficient and dynamic.

One of the things I enjoy about working at esveo is the chance to work on interesting projects. For over a year now, one of those interesting projects has been a custom spreadsheet-like application for displaying and analyzing different data sets.

One of the most important ideas behind a spreadsheet is that all the calculations are always live. If you update one cell, then all the other cells need to be updated based on the new values. This is often described as reactivity because the cells all need to react to changes in other cells.

Reactivity isn’t just limited to spreadsheets, though — it’s an important part of how web development works. When an application loads new data from an API, it needs to know how to update the UI to show the new data. Text might be updated, elements in a graph might move around, new rows of data might be added, a button that was previously disabled might become enabled, etc.

In both of these situations, as developers, we want a way to track the natural dependencies of our application. In the spreadsheet, if cell B5 depends on cell A1, then I want to make sure that B5 gets updated every time A1 changes — but also that it doesn’t get updated if some other, irrelevant cell gets updated. In a frontend application, if the state changes, I want to update any out-of-date DOM elements, but I don’t want to re-render my entire application when most of it has stayed the same.

In the case of our spreadsheet tool, we found the same tool to be helpful for both cases: Signals.

What are Signals?

A signal is a wrapper around some data — say, a number or an object — that allows you to react when that data changes. Signals come in a few different styles and APIs — a lot of frontend frameworks include their own signal library — but creating a signal might look something like this:

const counter = signal(0)

To react to changes to the signal, we use an effect function. For example, if we just want to write something to the console whenever a value changes, we might do this:

effect(() => {
    console.log(`counter is ${counter()}`);
})
// -> "counter is 0"

The counter() function returns the value of the signal that we created earlier. At the same time, the effect() function looks to see which signals were used inside the body of the effect. This means we automatically track dependencies — no need for React’s dependency arrays or manual calls to .subscribe().

The effect will always run once, immediately — this is necessary so that the initial tracking can get set up, and the effect knows which signals it’s listening to. After that, we can run it again by updating the value of counter:

counter.set(1);
// -> "counter is 1"

Here, the effect is writing something out to the console, but we could have a different effect that updated the DOM — changing text, hiding elements, adding a new row, etc. In practice, updating the DOM is usually done using a framework such as SolidJS, Vue, or Angular, which makes sure all the updates get scheduled at the right time to avoid performance issues. But under the hood, these frameworks all use signals and effects to manage these side effects.

Why are Signals exciting?

There’s a lot of buzz around signals right now. Both the Angular and Svelte frameworks have adopted signals. Vue has been using signals for a while but is currently replacing their rendering system with one that uses signals to massively reduce bundle sizes and improve performance. SolidJS, which has arguably pioneered the use of signals in modern frameworks, has just released SolidStart.

Perhaps more excitingly, TC39, the committee that decides what new features to add to Javascript, has begun looking at a proposal to add native signals to Javascript. That means that all the different frameworks using signals will be able to share the same foundation. It could also improve performance, debuggability (imagine the browser being able to show you a diagram of how all your state gets transformed and displayed), and make web apps smaller and more lightweight.

Personally, I’ve found signals to be really easy to work with, and I think that’s a big reason for the excitement. In React, I might juggle component state, separate global stores using Redux or MobX, and other assorted state values. But with, say, SolidJS, I can use signals for almost everything. It works as a global store, it works with component state, and it works everywhere in between — all with automatic dependency tracking and fine-grained, efficient DOM updates.