Gathering detailed insights and metrics for deepsignal
Gathering detailed insights and metrics for deepsignal
Gathering detailed insights and metrics for deepsignal
Gathering detailed insights and metrics for deepsignal
@deepsignal/core
This library is meant to expand on Preact's new `Signal` primitive to make it a viable state management solution at the scale of a full state management system by wrapping the built in primitive with a new `DeepSignal` model. This package in particular is
@deepsignal/react
This library is meant to expand on Preact's new `Signal` primitive to make it a viable state management solution at the scale of a full state management system by wrapping the built in primitive with a new `DeepSignal` model. This package is intended for
@deepsignal/preact
This library is meant to expand on Preact's new `Signal` primitive to make it a viable state management solution at the scale of a full state management system by wrapping the built in primitive with a new `DeepSignal` model. This package is intended for
alien-deepsignals
AlienDeepSignals 🧶 -alien signals, but using regular JavaScript objects
DeepSignal 🧶 - Preact signals, but using regular JavaScript objects
npm install deepsignal
Typescript
Module System
Node Version
NPM Version
93.1
Supply Chain
92.7
Quality
81.8
Maintenance
100
Vulnerability
100
License
deepsignal@1.6.0
Published on 13 Jan 2025
deepsignal@1.5.0
Published on 31 Jan 2024
deepsignal@1.4.0
Published on 11 Jan 2024
deepsignal@1.3.6
Published on 04 Aug 2023
deepsignal@1.3.5
Published on 04 Aug 2023
deepsignal@1.3.4
Published on 10 Jul 2023
TypeScript (84.19%)
JavaScript (15.53%)
Dockerfile (0.28%)
Total Downloads
1,601,207
Last Day
3,907
Last Week
15,426
Last Month
69,734
Last Year
1,064,228
222 Stars
753 Commits
7 Forks
5 Watching
2 Branches
34 Contributors
Latest Version
1.6.0
Package Id
deepsignal@1.6.0
Unpacked Size
279.65 kB
Size
38.96 kB
File Count
40
NPM Version
10.9.0
Node Version
22.12.0
Publised On
13 Jan 2025
Cumulative downloads
Total Downloads
Last day
-8.6%
3,907
Compared to previous day
Last week
-13.3%
15,426
Compared to previous week
Last month
24.3%
69,734
Compared to previous month
Last year
98.5%
1,064,228
Compared to previous year
Use Preact signals with the interface of a plain JavaScript object.
Proxy
that intercepts all property accesses and returns the signal value by default.$
prefix returns the signal instance: state.$prop
.deepsignal
wraps the object with a proxy that intercepts all property accesses, but does not modify how you interact with the object. This means that you can still use the object as you normally would, and it will behave exactly as you would expect, except that mutating the object also updates the value of the underlying signals.deepsignal
is designed to be lightweight and has a minimal footprint, making it easy to include in your projects. It's just a small wrapper around @preact/signals-core
.deepsignal
fully supports arrays, including nested arrays.deepsignal
converts nested objects and arrays to deep signal objects/arrays, allowing you to create fully reactive data structures.deepsignal
uses lazy initialization, which means that signals and proxies are only created when they are accessed for the first time. This reduces the initialization time to almost zero and improves the overall performance in cases where you only need to observe a small subset of the object's properties.deepsignal
uses stable references, which means that the same Proxy
instances will be returned for the same objects so they can exist in different places of the data structure, just like regular JavaScript objects.deepsignal
is written in TypeScript and includes type definitions, so you can use it seamlessly with your TypeScript projects, including access to the signal value through the prefix state.$prop
.deepsignal
can be used as a state manager, including state and actions in the same object.The most important feature is that it just works. You don't need to do anything special. Just create an object, mutate it normally and all your components will know when they need to rerender.
1npm install deepsignal @preact/signals
If you are using deepsignal
with Preact (@preact/signals
), you should use the deepsignal
import. You also need to install @preact/signals
.
1import { deepSignal } from "deepsignal"; 2 3const state = deepSignal({ 4 count: 0, 5}); 6 7const Count = () => <div>{state.$count}</div>;
1npm install deepsignal @preact/signals-react
If you are using the library with React (@preact/signals-react
), you should use the deepsignal/react
import. You also need to install @preact/signals-react
.
1import { deepSignal } from "deepsignal/react"; 2 3const state = deepSignal({ 4 count: 0, 5}); 6 7const Count = () => <div>{state.$count}</div>;
deepSignal
outside of the components, please follow the React integration guide of @preact/signals-react
to choose one of the integration methods.useDeepSignal
, no integration is required.Lit now supports Preact Signals, so you can also use deepsignal
in Lit.
1npm install deepsignal @lit-labs/preact-signals
If you are using the library just with Lit, you should use the deepsignal/core
import. You also need to install @lit-labs/preact-signals
and use its SignalWatcher
function.
1import { SignalWatcher } from "@lit-labs/preact-signals"; 2import { deepSignal } from "deepsignal/core"; 3 4const state = deepSignal({ 5 count: 0, 6}); 7 8class Count extends SignalWatcher(LitElement) { 9 render() { 10 return html`<div>${state.$count}</div>`; 11 } 12}
1npm install deepsignal @preact/signals-core
If you are using the library just with @preact/signals-core
, you should use the deepsignal/core
import. You also need to install @preact/signals-core
.
1import { effect } from "@preact/signals-core"; 2import { deepSignal } from "deepsignal/core"; 3 4const state = deepSignal({ 5 count: 0, 6}); 7 8effect(() => { 9 console.log(`Count: ${state.count}`); 10});
This is because the deepsignal
import includes a dependency on @preact/signals
, while the deepsignal/core
import does not. This allows you to use deep signals with either @preact/signals
or @preact/signals-core
, depending on your needs. Do not use both.
The usage is similar to Preact's signal
, but it works with objects and arrays and the access to the value and signal is reversed. By default, the object returns the value and not the signal:
state.prop
to access the value (you don't need to use state.prop.value
).state.prop
to mutate the value (you don't need to use state.prop.value
).state.$prop
to access the signal instance (only needed for performance optimizations).This Preact's signals example:
1import { signal, computed } from "@preact/signals"; 2 3const count = signal(0); 4const double = computed(() => count.value * 2); 5 6function Counter() { 7 return ( 8 <button onClick={() => (count.value += 1)}> 9 {count} x 2 = {double} 10 </button> 11 ); 12}
becomes like this with deepsignal
:
1import { deepSignal } from "deepsignal"; 2 3const state = deepSignal({ 4 count: 0, 5 get double() { 6 return state.count * 2; 7 }, 8}); 9 10function Counter() { 11 return ( 12 <button onClick={() => (state.count += 1)}> 13 {state.$count} x 2 = {state.$double} 14 </button> 15 ); 16}
You can also add actions inside the deep signal and use it as a state manager.
1import { deepSignal } from "deepsignal"; 2 3const store = deepSignal({ 4 count: 0, 5 get double() { 6 return store.count * 2; 7 }, 8 inc: () => { 9 store.count += 1; 10 }, 11}); 12 13function Counter() { 14 return ( 15 <button onClick={store.inc}> 16 {store.$count} x 2 = {store.$double} 17 </button> 18 ); 19}
deepSignal
1deepSignal<T extends object>(obj: T): DeepSignal<T>;
The deepSignal
function creates a new deep signal. You can read or mutate the underlying signal values by accessing the object's properties just like you would in a regular JavaScript object.
1import { deepSignal } from "deepsignal"; 2 3const state = deepSignal({ counter: 0 }); 4 5// Reads the value, logs: 0. 6console.log(state.counter); 7 8// Mutates the underlying signal. 9state.counter = 1; 10 11// Reads the value, logs: 1. 12console.log(state.counter);
Writing to a signal is done by mutating the object's properties. Changing a property's value will synchronously update every component and effect
that depends on its signal, ensuring your app state is always consistent.
1const state = deepSignal({ counter: 0 }); 2 3// Runs the first time, reads the value and outputs: <div>0</div> 4const Counter = () => <div>{state.counter}</div>; 5 6// Runs the first time, reads the value and logs: 0. 7effect(() => { 8 console.log(state.counter); 9}); 10 11// Mutates the underlying signal. 12state.counter = 1; 13// The effect runs again, logs: 1. 14// The component renders again, outputs: <div>1</div>.
get prop() { ... }
Using JavaScript getters with deepsignal
allows you to define computed properties that are based on the values of other properties, and ensures that the computed properties are automatically updated whenever the values of the other properties change. deepsignal
will convert getters to computed
values instead of signal
instances underneath.
1const state = deepSignal({ 2 counter: 1, 3 get double() { 4 return state.counter * 2; 5 }, 6}); 7 8// Runs the first time, reads value from the getter, logs: 2. 9effect(() => { 10 console.log(state.double); 11}); 12 13// Mutates the dependency. The effect runs again, logs: 4. 14state.counter = 2;
state.$prop
You can access the underlying signal of an object's property by using the $
prefix, like so: state.$prop
.
1const state = deepSignal({ counter: 0 }); 2 3// Runs the first time, read value from signal, logs: 0. 4state.$counter.subscribe(console.log); 5 6// Mutates the underlying signal. The subscription runs again, logs: 1. 7state.counter = 1;
array.$[index]
You can access the underlying signal of an array's item by using the $
prefix, like so: state.$[index]
.
1const array = deepSignal([0]); 2 3// Runs the first time, read value from signal, logs: 0. 4array.$[0].subscribe(console.log); 5 6// Mutates the underlying signal. The subscription runs again, logs: 1. 7array[0] = 1;
Please note that although the syntax is similar, there's a difference between objects and arrays. In objects, each prop has a signal counterpart (state.prop
-> state.$prop
) whereas in arrays, the array.$
prop returns a new array of signals, therefore the difference between array[index]
and array.$[index]
.
array.$length
Arrays can access the length signal with a property called array.$length
in the same way that objects have access to state.$prop
.
1const array = deepSignal([0]); 2 3// Runs the first time, logs: 1. 4array.$length.subscribe(console.log); 5 6// Mutates the array. The subscription runs again, logs: 2. 7array.push(1);
peek(state, "prop")
Chances are you will rarely need access to the underlying JavaScript object without subscribing to the current computation except when you have an effect that should write to another signal based on the previous value, but you don't want the effect to be subscribed to that signal. You can use peek(state, "prop")
to peek at the value of a signal:
1import { peek } from "deepsignal"; 2 3const state = deepSignal({ value: 0, effectCount: 0 }); 4 5effect(() => { 6 console.log(state.value); 7 8 // Whenever this effect is triggered, increase `effectCount`, but we don't 9 // want this effect to react to `effectCount`. 10 state.effectCount = peek(state, "effectCount") + 1; 11});
Note that you should only use peek()
if you really need it. Reading a signal's value via state.prop
is the preferred way in most scenarios.
For primitive values, you can get away with using store.$prop.peek()
instead of peek(state, "prop")
. But in deepsignal
, the underlying signals store the proxies, not the object. That means it's not safe to use state.$prop.peek().nestedProp
if prop
is an object. You should use peek(state, "prop").nestedProp
instead.
shallow(obj)
When using deepsignal
, all nested objects and arrays are turned into deep signal objects/arrays. The shallow
function is a utility that allows you to declare an object as shallow within the context of deepsignal
. Shallow objects do not proxy their properties, meaning changes to their properties are not observed for reactivity. This can be useful for objects that you don't want to be reactive or when you have an object that should not trigger UI updates when changed.
1import { deepSignal, shallow } from "deepsignal"; 2 3const shallowObj = { key: "value" }; 4const store = deepSignal({ 5 someData: shallow(shallowObj), 6}); 7 8// Accessing `store.someData` gives you the original object. 9console.log(store.someData === shallowObj); // true 10 11// Mutating `store.someData` does NOT trigger reactivity. 12store.someData.key = "newValue"; 13// No reactive update is triggered since `someData` is shallow.
In practice, this means you can have parts of your state that are mutable and changeable without causing rerenders or effects to run. This becomes particularly useful for large datasets or configuration objects that you might want to include in your global state but do not need to be reactive.
Although properties of a shallow object are not reactive, the reference to the shallow object itself is observed. If you replace the reference of a shallow object with another reference, it will trigger reactive updates:
1import { deepSignal, shallow } from "deepsignal"; 2 3const store = deepSignal({ 4 someData: shallow({ key: "value" }), 5}); 6 7effect(() => { 8 console.log(store.someData.key); 9}); 10 11// Changing the properties of `someData` does not trigger the `effect`. 12store.someData.key = "changed"; 13// No log output. 14 15// But replacing `someData` with a new object triggers the `effect` because store.someData is still tracked. 16store.someData = shallow({ key: "new value" }); 17// It will log 'new value'.
With shallow
, you have control over the granularity of reactivity in your store, mixing both reactive deep structures with non-reactive shallow portions as needed.
state.$prop = signal(value)
You can modify the underlying signal of an object's property by doing an assignment to the $
-prefixed name.
1const state = deepSignal({ counter: 0 }); 2 3// Runs the first time, read value from signal, logs: 0. 4state.$counter.subscribe(console.log); 5 6// Replace the signal with a new one. 7state.$counter = signal(10); 8 9// The subscription doesn't run this time; it's a different signal! 10state.counter = 1;
useDeepSignal
Only available on deepsignal
and deepsignal/react
, not on deepsignal/core
.
If you need to create a reference stable version of a deep signal that is hooked to a component instance you can use the useDeepSignal
hook:
1import { useDeepSignal } from "deepsignal"; 2// or 3import { useDeepSignal } from "deepsignal/react"; 4 5function Counter() { 6 const state = useDeepSignal({ 7 counter: 0, 8 get double() { 9 return state.counter * 2; 10 }, 11 }); 12 13 return ( 14 <button onClick={() => (state.count += 1)}> 15 Value: {state.$counter}, value x 2 = {state.$double} 16 </button> 17 ); 18}
If you need to reset your store to some initial values, don't overwrite the reference. Instead, replace each value using something like Object.assign
.
1const initialState = { counter: 0 }; 2 3const store = deepSignal({ 4 ...initialState, 5 inc: () => { 6 store.counter++; 7 }, 8 reset: () => { 9 Object.assign(store, initialState); 10 }, 11});
Take into account that the object that you pass to deepSignal
during the creation is also mutated when you mutate the deep signal. Therefore, if you need to keep a set of initial values, you need to store them in a different object or clone it before assigning it to the deepsignal.
You will only need access to the underlying signals for performance optimizations.
This works fine but Component
will render each time state.counter
changes.
1const state = deepSignal({ counter: 0 }); 2 3// Reads value from signal, outputs: <div>0</div> 4const Component = () => <div>{state.counter}</div>; 5 6// Mutates the underlying signal. The component is rerender again and outputs: <div>1</div> 7state.counter = 1;
We can pass the signal directly to JSX and Preact will mutate the DOM instead of rerendering Component
:
1const state = deepSignal({ counter: 0 }); 2 3// Reads value from signal, outputs: <div>0</div> 4const Component = () => <div>{state.$counter}</div>; 5 6// Mutates the underlying signal. The DOM is mutated to: <div>1</div> 7state.counter = 1;
This also works fine, but Parent
will render each time state.counter
changes.
1const state = deepSignal({ counter: 0 }); 2 3const Child = ({ counter }) => <span>{counter}</div>; 4const Parent = () => <Child counter={state.counter} />; 5 6// Mutates the underlying signal. Parent is rerender again. 7state.counter = 1;
You can pass the signal directly to the child:
1const state = deepSignal({ counter: 0 }); 2 3const Child = ({ counter }) => <span>{counter}</div>; 4const Parent = () => <Child counter={state.$counter} />; 5 6// Mutates the underlying signal. Parent is not rerender. 7state.counter = 1;
Be aware that if you do so, counter
will become a regular Preact signal. If you need to access or mutate its value inside Child
, you'd need to use counter.value
.
deepsignal
has TypeScript support, which means that you can use it seamlessly with your TypeScript projects. deepsignal
includes type definitions that provide type safety when working with deep signal objects, which can help you avoid common type-related mistakes and improve the overall quality of your code.
There is one caveat to consider when using deepsignal
with TypeScript: you need to use the non-null assertion operator when accessing signals. This is because mutations don't work unless the signals are optional, so TypeScript does not know that the signal instance will always be defined and it will treat it as a possibly-undefined value. To fix this, you can use the non-null assertion operator (!
) to tell TypeScript that the signal instance is definitely defined.
For example, consider the following code:
1const state = deepSignal({ 2 counter: 1 3}); 4 5console.log(state.$counter.value); // error: Object is possibly 'undefined'. 6console.log(state.$counter!.value); // 1
If we try to access the value
property of the $counter
signal, TypeScript will error because it does not know that the $counter
signal is defined.
The same happens with arrays:
1const array = deepSignal([1]); 2 3console.log(array.$[0].value); // error: Object is possibly 'undefined'. 4console.log(array.$![0].value); // 1
Note that here the position of the non-null assertion operator changes because array.$
is an object in itself.
DeepSignal exports two types, one to convert from a plain object/array to a deepSignal
instance, and other to revert from a deepSignal
instance back to the plain object/array.
You can use the DeepSignal
type if you want to manually convert a type to a deep signal outside of the deepSignal
function, but this is usually not required.
One scenario where this could be useful is when doing external assignments. Imagine this case:
1import { deepSignal } from "deepsignal"; 2 3type State = { map: Record<string, boolean> }; 4 5const state = deepSignal<State>({ map: {} });
If you want to assign a new object to state.map
, TypeScript will complain because it expects a deep signal type, not a plain object one:
1const newMap: State["map"] = { someKey: true }; 2 3state.map = newMap; // This will fail because state.map expects a deep signal type.
You can use the DeepSignal
type to convert a regular type into a deep signal one:
1import type { DeepSignal } from "deepsignal"; 2 3const newMap: DeepSignal<State["map"]> = { someKey: true }; 4 5state.map = newMap; // This is fine now.
You can also manually cast the type on the fly if you prefer:
1state.map = newMap as DeepSignal<typeof newMap>;
1state.map = { someKey: true } as DeepSignal<State["map"]>;
You can use the RevertDeepSignal
type if you want to recover the type of the plain object/array using the type of the deepSignal
instance. For example, when you need to use Object.values()
.
1import type { RevertDeepSignal } from "deepsignal"; 2 3const values = Object.values(store as RevertDeepSignal<typeof store>);
You can use the Shallow
type if you want to type a store that contains a shallow object.
1import type { Shallow } from "deepsignal"; 2 3type Store = { 4 obj: { a: number }; 5 shallowObj: { b: number }; 6}; 7 8const store = deepSignal<Store>({ 9 obj: { a: 1 }, 10 shallowObj: shallow({ b: 2 }), 11});
MIT
, see the LICENSE file.
This library is hugely inspired by the work of @solkimicreb with proxies and lazy initialization on @nx-js/observer-util
.
No vulnerabilities found.
No security vulnerabilities found.