Signals is a state management library from the Preact team. This is a safer alternative library.
The team provides a react compatibility library signals-react, but it comes with a very large downside: It alters React's internals. This is a big no-no for mainting compatibility with future versions of React, creating risk for your project. It also breaks compatibility with Next.js, which is a pretty good and popular React framework.
This library still lets you get the biggest benefit of Signals. When you render a signal directly within your JSX/TSX it creates a text node that updates when the signal updates, skipping the re-render of the component you render it within.
This library provides new hooks for reading the value of a signal in your React components. Instead of reading mySignal.value
somewhere in your component, use useSignalValue(mySignal)
instead.
IMHO, this is also helps readibility since it makes it more clear what can cause a component to re-render, and works better with hooks such as useEffect.
npm install signals-react-safe
mySignals.ts
import { signal } from 'signals-react-safe';
export const counter = signal(0);
Counter.tsx
(this component does NOT re-render when the signal updates, which is more performant)
import { counter } from "./mySignals";
export function Counter() {
return (
<div>
<div>Current Count: {counter}</div>
<button onClick={() => counter.value++}>Add One</button>
</div>
);
}
MixedCounter.tsx
(this component does re-render when the signal updates, which allows the value to be combined with non-signal values)
import { useState } from "react";
import { counter } from "./mySignals";
export function MixedCounter() {
const counterValue = useSignalValue(counter);
const [myCounter, setMyCounter] = useState(0);
return (
<div>
<div>Summed: {counterValue + myCounter}</div>
<button onClick={() => counter.value++}>Add One to signal counter</button>
<button onClick={() => setMyCounter(myCounter + 1)}>
Add One to useState counter
</button>
</div>
);
}
This library provides a bunch of exported function:
- Re-exports from the core Signals library:
signal
,computed
,effect
. - Copy/pasted hooks from @preact/signals-react:
useSignal
,useComputed
,useSignalEffect
. - New hooks unique to this library:
useSignalValue
,useSignalAndValue
,useComputedValue
.
Takes as input an existing signal, typically imported into your module from elsewhere in your app. If you want your component to create its own signal, use useSignal
instead.
It returns the value stored in the signal. When the signal's value changes, this hook will trigger a component re-render and return the new value.
Use this hook when you need to access the value stored in the signal, e.g. triggering a useEffect, or combining with non-signal values.
If you want to combine the value with other signal values, consider using useComputed
or useComputedValue
to be more performant.
Example:
import {count} from '../signals';
function Count() {
const countValue = useSignalValue(count);
useEffect(() => {
localStorage['count'] = `#{countValue}`;
}, [countValue]);
return <div>{count} <button onClick={() => count.value++}>Add one</button></div>
}
Creates a signal and returns it, along with its value. Triggers a component re-render in order to return the latest value. Use this to replace useSignal
if you need to read the value of a component's signal within the component.
It returns an array with two elements:
- The signal.
- Its value.
Example:
function MyCounter() {
const [count, countValue] = useSignalAndValue(0);
useEffect(() => {
localStorage['count'] = `#{countValue}`;
}, [countValue]);
return <div>{count} <button onClick={() => count.value++}>Add one</button></div>;
}
Take a function as its parameter and returns a value. It's useful when that function uses multiple signals.
A new value will be generated if any of the referenced signals are updated, causing the component to re-render.
Example:
import {count} from '../signals';
function MyMultiplier() {
const multiplier = useSignal(1);
const multipliedValue = useComputedValue(() => multiplier.value * count.value);
useEffect(() => {
localStorage['multiplied'] = `#{multipliedValue}`;
}, [countValue]);
return <div>{multiplier} <button onClick={() => multiplier.value++}>Add one</button></div>;
}
The new hooks that directly return values will trigger re-renders of the component. This would also happen if you read a signal's value inside a component when using @preact/signals-react. If you want to avoid that re-render, place your signal directly in your JSX.
Created by Jon Abrams (2023)
Contains code from Preact Signals
Idea inspired by satoshi-cyber's suggestion.