mali-signali
This package provides a lightweight, framework-agnostic TypeScript library for reactive state management.
Signals
Signals are the basic units of state. They hold a value and are represented as a tuple of read()
and update()
functions.
import { signal } from 'mali-signali';
const [count, setCount] = signal(0);
console.log(count());
setCount(1);
console.log(count());
As a convenience, the signal()
function actually returns an instance of Array subclass with
read()
and update()
methods, so it can be passed around as a single object.
const count = signal(0);
console.log(count.read());
count.update(1);
console.log(count.read());
Memos
Memos are derived (a.k.a. computed) signals. They are created by passing a function that computes the value of the memo based on the values of other signals.
They are read-only and are updated automatically when the signals they depend on change.
The memo's compute function MUST be idempotent (i.e., it must not have side effects and must always return the same result for the same values of signals it depends on).
import { signal, memo } from 'mali-signali';
const [count, setCount] = signal(0);
const doubleCount = memo(() => count() * 2);
console.log(count());
console.log(doubleCount());
setCount(6);
console.log(count());
console.log(doubleCount());
Effects
Effects are functions that are re-run whenever the signals (or memos) they depend on change. They are what makes the state management reactive.
import { signal, memo, effect } from 'mali-signali';
const [count, setCount] = signal(0);
const doubleCount = memo(() => count() * 2);
effect(() => {
console.log('doubleCount:', doubleCount());
});
setCount(6);
setCount(10);
Effect functions can return a cleanup function that is called before the effect is called again. This can be used to clean up resources or cancel subscriptions.
import { signal, effect } from 'mali-signali';
const [count, setCount] = signal(0);
effect(() => {
console.log('count:', count());
const id = setInterval(() => {
setCount(count() + 1);
}, 1000);
return () => {
clearInterval(id);
};
});
The effect()
function itself returns a callback which, when called, cancels the effect (i.e. removes that effect from dependencies of referenced signals).
import { signal, effect } from 'mali-signali';
const [count, setCount] = signal(0);
const cancel = effect(() => {
console.log('count:', count());
});
setCount(1);
setCount(2);
cancel();
setCount(3);
Batching
The batch()
function can be used to batch updates to signals. This can be useful when multiple signals are updated in quick succession, as it prevents unnecessary re-runs of effects.
import { signal, effect, batch } from 'mali-signali';
const [a, setA] = signal(1);
const [b, setB] = signal(2);
const sum = memo(() => a() + b());
effect(() => {
console.log(a(), '+', b(), '=', sum());
});
setA(3);
setA(4);
setB(5);
batch(() => {
setA(6);
setB(7);
setB(8);
});
Store
The createStore()
function can be used to create an independent store that holds a collection of signals, memos, and effects. It provides a way to use same instance of library in multiple places without interfering with each other.
The signals, memos, and effects from one store are isolated from those of another store and MUST NOT be used interchangeably between stores.
The createStore()
returns an object with signal()
, memo()
, effect()
, and batch()
functions that work the same way as the global functions, but operate on the store they were created with.
The global functions signal()
, memo()
, effect()
, and batch()
are simply shortcuts for the default library-global store.
License
MIT
See also