Blog

Signals in JavaScript: A soon standard or overhyped?

Written by Florian Bauernhofer | 12. März 2025
Photo by Carlos Alberto Gómez Iñiguez on Unsplash

When building modern web applications, efficient state management is crucial for delivering smooth, responsive user experiences. This is where signals come in — a concept designed to streamline reactive state management.
 

In this blog post, I will explain what signals are, where they come from, and how simple they can be by building a basic signal implementation in TypeScript. Additionally, we’ll explore how different web frameworks implement signals and compare them to traditional state management. Finally, we’ll look at how you can use both concepts in your apps.

What are signals?

At their core, signals are quite simple, yet powerful. They act as a primitive data type that can hold a value while automatically keeping track of any dependencies linked to it. Whenever the signal’s value changes, all dependencies are notified, triggering the necessary computations and effects to re-run.

This automatic update process means signals can be used to manage application state in a way that ensures only the parts of the user interface reliant on that specific signal are re-rendered. As a result, signals enable fine-grained reactivity by preventing unnecessary updates to unrelated components.

The concept of signals has been around for quite some time. Knockout.js was among the early JavaScript frameworks to adopt this concept in 2010. However, signals have historical precedence, being employed in non-web environments such as the QT framework for C++ which was released in 1995, so they are not an entirely new concept.

In recent months, various JavaScript frameworks such as Solid, Vue, and Angular implemented their own versions of signals and there are even packages which implement signals for mobile frameworks like Flutter.

Let’s build a signal implementation in TypeScript

In their simplest way, a signal is just a function which holds a value, a getter function, a setter function and its subscribers. We can make use of JavaScripts get and set keywords for that.

export function signal<T>(initialValue: T) {
let value = initialValue;

return {
get value() {
return value;
},
set value(v) {
value = v;
},
};
}
}
At this point, our signal implementation lacks the most important feature, named reactivity. The ultimate goal of a signal is to notify all subscribers of the signal, when the value has changed, so they can react on the value change and, for example, update the UI accordingly.

So what we need is a way to let the signal know its subscribers. In the simplest way, a subscriber is just a function:

export function signal<T>(initialValue: T) {

let value = initialValue;

const subscribers: ((value: T) => void)[] = [];

function subscribe(subscriber: (value: T) => void) {
subscribers.push(subscriber);
}

return {
get value() {
return value;
},
set value(v) {
value = v;
},
subscribe,
};
}


All we need to do now is to call all subscriber functions whenever the value changes:

export function signal<T>(initialValue: T) {

let value = initialValue;

const subscribers: ((value: T) => void)[] = [];

function subscribe(subscriber: (value: T) => void) {
subscribers.push(subscriber);
}

function notifySubscribers() {
for (const subscriber of subscribers) {
subscriber(value);
}
}

return {
get value() {
return value;
},
set value(v) {
value = v;
notifySubscribers();
},
subscribe,
};
}
}


We can now use our signal like this:

const count = signal(0);

count.subscribe((value) => {
console.log(value); // will be called on every value change
});

count.value = 1;
count.value = 2;
count.value = 3;

// Output:
// 1
// 2
// 3


For simplicity I abstracted quite a bit from the signal implementations of modern web frameworks. They wouldn’t provide a subscribe method to implement dependency tracking. Instead, they would utilize some form of effect mechanism. This mechanism would accept a callback as a parameter, similar to our subscribe function. The framework would then identify the signals being referenced within the effect to add the relevant dependencies to the correct signal(s).

If you want to dive deeper into reactivity libraries, I can recommend this blog post from Ryan Carniato, the developer of Solid.

Signals in modern web-frameworks (vs React useState)

The following frameworks have built-in implementations for signals. The different terms for their implementations may be confusing at first, but ultimately they all use the concept of signals.

  • VueJS — ref
  • Angular — signal
  • SolidJS — createSignal
  • Svelte — rune
  • Preact — signal
  • Qwik — signal

Now we can see that the most popular frontend library React is missing, but why is that?

React doesn’t use signals because its update mechanism is centered around the virtual DOM and diffing process, rather than fine-grained reactive signals. When state or props change, React creates a new virtual DOM and compares it (diffs) against the previous one. This allows React to figure out what parts of the actual DOM need to be updated. Signals could only be leveraged when you bypass Reacts diffing algorithm, which would essentially go against Reacts principles of declarative programming.

Contrary to a common misconception on the web, when you use the useState() hook in React, you are not using a signal. However, there are quite a few advantages when using signals over traditional state mangement:

1. Performance increase

Signals are designed to update only the parts of the UI that depend directly on them. When a signal’s value changes, only the components or parts of the DOM that reference that signal are re-rendered. With useState, the entire component that uses the state is re-rendered whenever the state changes, regardless of whether all parts of the component depend on that state or not. This can lead to unnecessary re-renders and performance overhead.

2. Sharing state between components

You are not bound to use signals only inside of components. You can share a signal between components, which essentially means that you can share state between components. This prevents state variables being passed down the component tree and events being passed back to the component where that state is being held, which also makes the whole codebase more readable. You should still use patterns like the Container/Component pattern though, and avoid using shared signals in representational components.

3. Simplicity

Signals are easy to understand and easy to use for developers. Since they automatically update only the affected parts of the UI, there’s less need for complex memoization (useMemo, useCallback, etc.) to optimize performance and prevent unnecessary renders. They are more general-purpose and can also be used outside of the context of UI rendering. That makes signals useful for managing state in various parts of an application, not necessarily just components, whereas useState is typically only being used inside of components.

4. Developer experience

Signals can also drastically improve developer experience when compared to useState. When you want to react to a state change in React for example, you need to use the useEffect hook and explicitly add all dependencies inside the dependencies array, which you want to subscribe to. It is quite easy to make mistakes with larger components, which leads to a lot of unnecessary re-renders and is also an additional effort you have to take as a developer.

When using signals, however, the signals keep track of the dependencies themselves. In most frameworks, such a subscription is added when using the getter function of the signal inside of an effect function, as I mentioned in the first chapter. This means the developer does not have to explicitly set them, or subscribe to them (as I did in the simple implementation above).

const counter = ref(0);

computed(() => {
console.log(counter);
});


In Vue for example, this code will automatically add the function provided inside of the computed function to the signals dependencies list, and execute it when the value changes. Simple as that.

Will signals replace existing state management libraries?

Global signals could in theory be used for application-wide state management. You can use them in your components and get all the reactivity features that come with them. At first glance, using signals for app-wide state might seem like a no-brainer, but it is worth taking a closer look.

Signals may offer the advantage of having much less boilerplate code and a very simple API to use them, especially for small or medium sized applications. For performance-critical applications such as real-time or gaming UIs, they may offer better performance due to fine-grained reactivity.

However, as applications grow in size, the value of state management libraries increase. Redux, Pinia, or TanStack Store, all enforce a strict unidirectional data flow with a centralised state store. This makes debugging and understanding state flow easier, especially in large applications. They also provide powerful development tools with debugging, state inspection, and more.

If your app involves a complex global state that’s shared across many components, using a state management library is likely still your best bet. These libraries offer a structured approach, especially when managing large-scale state interactions. On the other hand, if your app’s global state is shared by only a handful of components, opting out of a state management library can save you time and reduce boilerplate. However, this comes with the trade-off of potentially more difficult debugging and a trickier flow to follow as your app scales.

In short, signals haven’t quite caught up in some areas, making state management libraries highly valuable in certain scenarios, for example to manage global state. As with most things in software development, the best approach depends on the specific needs of your app — whether signals or a library will make it easier for you to manage global state.

Signals part of standard JavaScript?

With the growing popularity of signals, there’s been increasing interest in seeing them standardised in JavaScript. The push for this standardisation centers around a proposal to implement signals natively within JavaScript, aiming to create a unified approach to handling reactivity and event-driven programming. This effort seeks to bring more consistency to the language, particularly across the various frameworks and libraries that developers rely on today. By adopting a standardised approach, the proposal hopes to reduce the current fragmentation in the JavaScript ecosystem, where different tools and frameworks often handle state and reactivity in incompatible ways.

This would not only enhance compatibility but also simplify the development process, making it easier for developers to switch between libraries or frameworks without having to relearn different approaches to handling state changes.

The proposal led by Rob Eisenberg and Daniel Ehrenberg is already available, and if you want to try it, you can use a polyfill.

Conclusion

In conclusion, signals are emerging as a powerful tool for state management, particularly in smaller applications or scenarios where fine-grained reactivity is critical. They offer several advantages, such as better performance by re-rendering only the parts of the UI directly dependent on a signal, reduced boilerplate code, and a simpler developer experience. Signals also make state sharing between components easier, without needing to pass data down component trees or rely on complex hooks and optimizations like in traditional state management.

However, while signals shine in terms of simplicity and efficiency for small to medium apps, they are not yet a replacement for established state management libraries, especially in larger applications. These libraries bring structured patterns, centralized state, and robust debugging tools that help manage complex, app-wide state more easily and prevent issues as the application grows. The unidirectional data flow and powerful developer tools they offer can make state management and debugging more predictable, which is often crucial in larger projects.

Ultimately, the choice between signals and traditional state management libraries depends on your app’s scale and needs. Signals might be perfect for apps that prioritize performance and simplicity, while state management libraries are still the go-to for large-scale applications requiring more organization and clarity in managing state across many components.

Personally, I think there is place for both concepts, even within an application. I like to leverage the advantages of signals within components or a scoped state, for example for a specific feature, while still using established state libraries for global state management.

It’s time to build

At &‌amp, we sketch, craft, and ship software for SMEs and big corporates. We maintain clear and consistent code quality in all settings to build and ship software that we love!