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
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);
Counter Component
import React from "react";
import { signal } from "@preact/signals-react";
const count = signal(0);
const Counter = () => <button onClick={() => count.value++}>{count}</button>;
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>;
};
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>
);
};
Live Demo: Counter Demo
Thank you for reading 😊
Got any questions or additional? please leave a comment.
Must Read If you haven't
How to create JSX template engine from scratch
Rahul Sharma ・ Nov 23 '22
How to cancel Javascript API request with AbortController
Rahul Sharma ・ Apr 9 '22
13 Typescript Utility: A Cheat Sheet for Developer
Rahul Sharma ・ Apr 2 '22
Catch me on
Youtube Github LinkedIn Medium Stackblitz Hashnode HackerNoon
Top comments (17)
This is only for
preact
? Because of the custom jsx renderer that can hook into singals?Ok, I have just found out there is a specfic version for React also from the same authors.
npmjs.com/package/@preact/signals-...
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?
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.
hi Rahul, can you use object with signal. thanks in advance.
yes you can, check this section here on docs
Combining multiple updates into one
preactjs.com/guide/v10/signals#usa...
i have tried, but my UI didn't update after click add new Todo.
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.
thank you, Khoa, i will try it.
i've tried but not working like you said, could you plz create the sandbox this case? thanks in advance.
@nhd2106 I've added working example.
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.
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...
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
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...
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.
isnt this 1:1 with the preact docs?
preactjs.com/guide/v10/signals#usa...
If you would like to use signals with react try my lib. It made for react.
npmjs.com/package/react-signaler