Skip to content

Manage State using Signal in React

Published:

If you are a Frontend developer then you might have heared about Signals. Here is what Signals are, what are their advantages and how you can use them with React.

🔷 What are Signals ?

🔷 How to use Signals ?

Preact is an alternative library which uses same API as React. But the size of Preact is 3kb which is much smaller than React, which is about 45kb hence it ships less code to the browser.

Recently, Preact have announced a new package called @preact/signals-react which is available to install in React as well and using this package we can use Signals in React and use it for state management.

In your React project you can install the package using the below command.

npm install @preact/signals-react

Once installed now you can import signal() function from @preact/signals-react and create new Signal.

🔷 You can access the value of Signal using signal.value and set the value of the signal using signal.value = newValue

🔷 In contrast to state, Signals can be created outside of components. Updating Signals does not require the use of the setState method, unlike state. The value of a Signal can be updated directly using the signal.value property.

import { signal } from "@preact/signals-react";

// ✅ Create a new Signal as shown below
const count = signal(0);

// ✅ Create a function to update Signal value
function increaseCounter() {
  count.value += 1;
}

Several popular frameworks, including Vue, Preact, Solid, and Qwik, support Signals. However, there are slight differences in how Signals work and their syntax among these frameworks. Therefore, if you plan to use Signals in any of the mentioned frameworks, it is recommended to refer to the respective framework’s documentation.

However, f you are interested in learning how to use Signals in React, you can continue reading this article.

📌 Benefits of using Signal

Here are some of the benefits of using Signal over State.

✅ It does not re-render the entire component when Signal value changes

When Signal value changes then it does not re-render the entire component unlike State in React. Instead it only re-renders that specific part of the DOM where the value of signal is being used.

Here is an example of a counter application using State. When the Increase Counter button is clicked, the State is updated and the entire component is re-rendered. This can lead to performance issues, especially for large components, as a single state update triggers the re-rendering of the entire component.

// CounterWithState.jsx -- using State

import React, { useState } from "react";

function CounterWithState() {
  const [count, setCount] = useState(0);

  // ✅ The below line gets invoked when State is updated
  console.log("CounterWithState Rendered!");
  return (
    <>
      <h4>Inside "CounterWithState" component</h4>
      <h3>The count is {count} </h3>
      <button onClick={() => setCount(count + 1)}>Increase Counter</button>{" "}
    </>
  );
}
export default CounterWithState;

Now we will build the same counter application but now using Signal.

// CounterWithSignal.jsx -- using Signals

import React from "react";
import { signal } from "@preact/signals-react";

// ✅ Create a signal
export const count = signal(0);

const CounterWithSignal = () => {
  // ✅ The below line does not gets invoked then Signal value changes
  console.log("CounterWithSignals Rendered!");

  return (
    <div>
      <hr></hr>
      <h3>Inside "CounterWithSignals" component</h3>
      <h3> Current value count is {count}</h3>
      <button onClick={() => count.value++}> Increase Counter</button>
      <hr></hr>
    </div>
  );
};

export default CounterWithSignal;

Now in the Counter application built with Signal if we click on Increase Button then it does not re-render the entire CounterWithSignal component, Instead it only updates the specifc DOM element where we are using the Signal value.

As you can see in the below GIF when Increase Counter is clicked in Counter App made using Signal, then it is not re-rendering the entire component. Instead it is only re-rendering that specific part of the DOM where the Signal value is being used.

Counter_gif

So using Signal we can avoid unnecessary rerender of the entire component when a small part is being changed.

✅ Using Signal you can directly pass data from one component to another without prop drilling or Context API

Using Signal we can directly pass data across components hence we don’t need to use prop drilling or Context API to pass data across different components.

🔷 In the below example we have created a new component as TestChild and inside it we can directly import the count Signal from the CounterWithSignal component and hence it removes the need of using prop drilling and Context API to pass data across components.

// TestChild.jsx

import React from "react";

// ✅ You can directly import the Signal from other component without need to prop drill or Context API
import { count } from "./CounterWithSignal";

function TestChild() {
  return (
    <>
      <h5>Inside the TestChild Component</h5>
      <h3>Current value of count is {count}</h3>
      <hr></hr>
    </>
  );
}

export default TestChild;

🔷 And since we are using count signal value in the TestChild component hence the TestChild component will automatically subscribe to the changes of counter signal. Now whenever the value of counter signal changes in the CounterWithSignal component then TestChild component will also re-render automatically and receive the updated value of counter signal.

Here the entire TestChild component won’t re-render, instead the specific part where we have used the Signal value, only that specific part will be re-rendered.

✅ Updating signal value in parent component does not re-render all the child components

Suppose you have a parent component as App and inside it you have two siblings components as ComponentA and ComponentB. Now if any state gets updated in App component it will rerender all of its child components. Even if we are not using that state in the child components then also all the child components will still be re-rendered. This becomes a problem specially when you have lot’s of child components being rendered inside a parent component.

But if you use Signal instead of state then even if the value of Signal gets updated in the parent component the child will not re-render until unless the child component uses that signal value. Hence this way we can prevent unnecessary re-render of the child components which is not using state value.

✅ effect() function to track when Signal value changes

Signal provide us a function as effect which is quite similar to useEffect hook. But in effect function we don’t have to pass dependencies array like the useEffect hook. It’ll automatically detect dependencies being used inside of the effect function and call effect only when dependencies change.

import React from "react";
import { signal, effect } from "@preact/signals-react";

const count = signal(0);

function Counter() {
  // ✅ Below effect function runs when Signal value changes
  effect(() => {
    console.log("ConterWithSignals Rerendered!!");
    console.log(count.value);
  });

  return (
    <>
      <button
        onClick={() => {
          count.value++;
        }}
      >
        Increase Counter
      </button>
    </>
  );
}

export default Counter;

In the above code when we click on the Increase Counter button then it changes the value of signal and since we have used the signal value inside of the effect() function hence it will take the signal value as the dependency and hence the statements written inside of effect() function gets run.

This was a brief introduction of Signals in React.

⚒️ Advanced usage of Signals

You can refer to the Preact documentation to see some more advanced usage of Signals. Here is the link to the Preact documentation.

Signals – Preact Guide