Unlocking React’s Potential with Signals: A Step-by-Step Guide to Implementing Signals

Furkan Kaynak
4 min readNov 3, 2023

--

Unlocking React’s Potential with Signals: A Step-by-Step Guide to Implementing Signals

Unlocking React’s Potential with Signals

React.js has long been a powerhouse for building dynamic web applications, but there’s always room for innovation and improvement. Signals are a powerful and versatile state management solution that can take your React projects to new heights. In this deep dive, we’ll unravel the mystery behind Signals, exploring what they are, how they work, why they’re valuable, and when they may not be the best fit for your application.

What Are Signals?

Signals are a modern approach to state management in React applications. At their core, they are lightweight, event-driven objects that help you manage and propagate changes across your application’s components. Unlike traditional state management solutions, Signals simplify the flow of data, making your codebase more manageable and your application more predictable.

How Do Signals Work?

Signals operate on the principle of publishing and subscribing to events. When a component needs access to specific data, it subscribes to the corresponding Signal. When the data changes, the Signal broadcasts the update to all subscribed components, ensuring that your application remains in sync.

When Might Signals Be the Right Choice?

In React development, Signals offer more than just a state management innovation; they also provide a way to reduce the reliance on `useMemo` and `memo`. With their efficient event-driven system that automates data updates and component rendering, Signals minimize the need for manual memoization, resulting in cleaner, more maintainable code. Signals truly shine in larger projects with complex data flows, making them the right choice when scalability and efficient state management are paramount. This not only enhances application performance but also provides a more enjoyable development experience.

When Might Signals Not Be the Right Choice?

Signals may not be the best choice in smaller applications with straightforward state management needs. For teams unfamiliar with Signals, the initial learning curve could be a consideration. If your project is tightly integrated with third-party libraries that aren’t Signal-compatible or involves significant migration from an existing state management solution, it may be more practical to explore alternatives. Assess your project’s constraints and requirements to determine if Signals align with your specific use case.

Let’s Implement Signals

In this section, we’ll begin with a very basic implementation to help you get started with Signals in a clear and straightforward manner.

Implementing an Observer: Combining State and Components

One of the key components of our Signal state management system is the observer. This observer is not your typical observer pattern; it also behaves like a React component. The beauty of this approach lies in its ability to seamlessly synchronize state changes and component rendering.

How does it work?

When we set the value of a Signal, our observer immediately becomes aware of the change. It acts as a bridge between the signal’s value and the component that displays it. As a result, any update in the Signal triggers the observer to notify the connected component, which, in turn, shows the updated value.

import { useSyncExternalStore } from "react";

function SignalValue({ data }) {
const value = useSyncExternalStore(data.subscribe, data.getSnapshot);

return value;
}

class Signal {
constructor(value) {
this._value = value;
this._listeners = new Set();
}

// Subscribe to changes in the Signal
subscribe = (fn) => {
this._listeners.add(fn);

return () => {
this._listeners = new Set([...this._listeners].filter((cb) => cb !== fn));
};
};

// Get the current snapshot of the Signal state
getSnapshot = () => {
return this.value;
};

// Notify all listeners
notifyListeners = () => {
this._listeners.forEach((updaterFn) => updaterFn(this));
};

// Get the current value of the Signal
get value() {
return this._value;
}

// Set the value of the Signal and notify listeners
set value(value) {
this._value = value;
this.notifyListeners();
}
}

// Integrate Signal with React components
Object.defineProperties(Signal.prototype, {
$$typeof: { configurable: false, value: Symbol.for("react.element") },
type: { configurable: false, value: SignalValue },
props: {
configurable: true,
get() {
return { data: this };
}
},
ref: { configurable: true, value: null }
});

// Create a new Signal with an initial value
export const signal = (value) => new Signal(value);

In our quest to harness the power of Signals for state management in React, we’ve devised an elegant solution that combines state and components seamlessly. Let’s dive into the code and see how it works. First, we introduce the `Signal` class, a fundamental element of our system. It’s designed to hold and manage a value, which can be observed by components. When the value of a `Signal` changes, it notifies any subscribed observers to update accordingly. Next, we have the `SignalValue` component, which plays the role of an observer. This component not only displays the value of a `Signal` but also automatically re-renders whenever the `Signal` value changes. The component we use the signal on will not be re-rendered, but our value will always appear up to date.

It’s a powerful synergy where data updates trigger immediate component updates, providing a highly responsive and efficient system for managing your application’s state.

Part 2: Unlocking React ’s Potential with Signals: A Step-by-Step Guide to Implementing Computed

Please Follow me for more interesting content

--

--

Furkan Kaynak

Senior Full Stack Developer @obsstech | Passionate about JavaScript, ReactJS, NodeJS, and AWS