React.js state management using signals
Rahul Sharma

Rahul Sharma @devsmitra

About: Passionate JavaScript Developer 🚀 | Crafting Web Magic ✨ | Code Enthusiast 💻

Location:
Bangalore, India
Joined:
Mar 14, 2022

React.js state management using signals

Publish Date: Sep 21 '22
57 18

A signal is an object that has a value and can be observed for changes. It is similar to a state, but it is not bound to a component. It can be used to share data between components. It updates the components when the signal changes and updates the UI without re-rendering the whole component.

This lets us skip all of the expensive rendering work and jump immediately to any components in the tree that access the signal's value property.

In this article, I'll be using signals with React.

Installation

npm i @preact/signals-react
Enter fullscreen mode Exit fullscreen mode

Create a signal

We can create state(signal) using signal function, signal function takes default signal(value) as an parameter and returns Proxy object. The value of the signal can be accessed using the signal.value property. We can also set the value of the signal using signal.value = newValue.

import { signal } from "@preact/signals-react";
const count = signal(0);
Enter fullscreen mode Exit fullscreen mode

Counter Component

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

const count = signal(0);
const Counter = () => <button onClick={() => count.value++}>{count}</button>;
Enter fullscreen mode Exit fullscreen mode
NOTE: React Hooks can only be called inside the root of the component, Signal can be used outside of a component.

Effect

We don't have to pass dependencies array like the useEffect hook. It'll automatically detect dependencies and call effect only when dependencies change.

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

const count = signal(0);
const Counter = () => {
  effect(() => console.log(count.value));
  return <button onClick={() => count.value++}>{count}</button>;
};
Enter fullscreen mode Exit fullscreen mode

Advanced Usage

When working with signals outside of the component tree, you may have noticed that computed signals don't re-compute unless you actively read their value.

const count = signal(0);
const double = computed(() => count.value * 2);

const Counter = () => {
  effect(() => console.log(count.value));
  return (
    <div>
      <h1>{double}</h1>
      <button onClick={() => count.value++}>{count}</button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Live Demo: Counter Demo


Thank you for reading 😊

Got any questions or additional? please leave a comment.


Must Read If you haven't

Catch me on

Youtube Github LinkedIn Medium Stackblitz Hashnode HackerNoon

Comments 18 total

  • Duoc95
    Duoc95Mar 2, 2023

    hi Rahul, can you use object with signal. thanks in advance.

    • Rafael Melo
      Rafael MeloMar 4, 2023

      yes you can, check this section here on docs

      Combining multiple updates into one
      preactjs.com/guide/v10/signals#usa...

      • Duoc95
        Duoc95Mar 7, 2023
        import React from 'react'
        import {  signal } from '@preact/signals-react'
        
        function Todos(props) { 
            const todos = signal([
                { name: '123'},
            ])
        
            const addToDo = () => {
                todos.value = [...todos.value, { name: '123123'}]
            }
        
            return (
            <div>
                <button onClick={() => {
                    addToDo();
                }}>add to do</button>
                {
                    todos.value.map(({ name }, index) => (<div key={index} style={{
                        fontWeight: 'bold'
                    }}>{name}</div>))
                }
            </div>
          )
        }
        
        export default Todos
        
        
        Enter fullscreen mode Exit fullscreen mode

        i have tried, but my UI didn't update after click add new Todo.

        • Dang Khoa
          Dang KhoaMar 31, 2023

          I found that todos value will be create new when you change value. Just move it outside a Todos component to make it works properly.

          const todos = signal([
                  { name: '123'},
              ]);
          function Todos(props)
          ...
          
          Enter fullscreen mode Exit fullscreen mode
          • Duoc95
            Duoc95Apr 5, 2023

            thank you, Khoa, i will try it.

            • Duoc95
              Duoc95Apr 5, 2023

              i've tried but not working like you said, could you plz create the sandbox this case? thanks in advance.

  • Rafael Melo
    Rafael MeloMar 4, 2023

    isnt this 1:1 with the preact docs?
    preactjs.com/guide/v10/signals#usa...

  • Marcos Emmanuel López
    Marcos Emmanuel LópezMar 15, 2023

    Hi Rahul!
    Thank you so much for this guide. I have a little problem, well, I don't know if it's a problem but it seems odd.
    I was messing with creating a signal that contained an object like this:
    const counters = signal({
    counter1: 0,
    counter2: 0,
    })

    and in my component I've declared an effect to see that value change, like this:
    effect(() => console.log(count.value))

    And then I want to update one of those values like this:
    const increment = (val) => {
    counters.value = {
    ...counters.value,
    counter1: count.value.counter1 += val
    }
    }

    But what I notice in the console is that the value of the signal prints more and more everytime it changes, for example, when I press increment, it will print the object 1 time, if I press it again, it prints the object 2 times, If I press it againt, it prints the object 3 times, and so on.

    Do you know why this happens?

    EDIT:
    Ok, I've just found out that if instead of using effect I use batch, the console log fires only 1 time per change:
    batch(() => {
    console.log(count.value)
    })

    But I still don't understand why that happens?

    • danVladAndreev
      danVladAndreevApr 19, 2023

      This is probably because you effect isn't destroyed after the re-render.
      You can try this:

      import {useSignalEffect} from '@preact/signals-react';
      ...
      useSignalEffect(() => console.log(count.value));

      This should create the effect only once.

  • Eyal Lapid
    Eyal LapidOct 9, 2023

    This is only for preact? Because of the custom jsx renderer that can hook into singals?

  • Indrajeet Nikam
    Indrajeet NikamNov 6, 2023

    Hi Rahul, Great article.

    I have a question though, Is there any way to restrict access to signals so that signals are not-misused by team members in the future (let's say after 2-3 years).

    Because the way I understand it, anybody can change it right? It's usually not an issue for senior folks in team but junior devs tend to misuse such highly flexible mechanism.

    How can I prevent that? that's my biggest concern for using signals in our massive codebase at company.

    • Rahul Sharma
      Rahul SharmaNov 6, 2023

      I don't think we can restrict anyone from using it, because it's the public library.
      One thing you can do is add an eslint rule for such imports. I usually do that to forcefully adopt "Tree Shaking" from any library.

      FYR: mui.com/material-ui/guides/minimiz...

      {
        "rules": {
          "no-restricted-imports": [
            "error",
            {
              "patterns": ["@mui/*/*/*"]
            }
          ]
        }
      }
      
      Enter fullscreen mode Exit fullscreen mode
      • Indrajeet Nikam
        Indrajeet NikamNov 6, 2023

        This is certainly a way to enforce imports but my main concern was slightly different.

        I want to avoid concerns like somebody changing the state of signal from outside the component where it is not supposed to be accessed. I searched a lot for the solution,

        I came to the conclusion that, only sensible way of doing that is through this pattern/recipe

        Image description

        You can read more here, preactjs.com/guide/v10/signals#man...

        I tested it in code-sandbox and it works as expected while creating different signal instances for different components. you can play with it here,
        codesandbox.io/s/react-16-9-0-fork...

        • Rahul Sharma
          Rahul SharmaNov 6, 2023

          I got your point.

          We can create small stores (like user, todo, etc) using the signal. All the actions related to user/todo should use that. Svelte and Solid.js follow the same pattern for creating custom stores.

  • Péter Hragyil
    Péter HragyilSep 13, 2024

    If you would like to use signals with react try my lib. It made for react.

    npmjs.com/package/react-signaler

  • pianoboy
    pianoboyFeb 20, 2025

    You mast find this,I think it is better!:npmjs.com/package/react-use-signal

Add comment