@known-as-bmf/store
v3.0.7
Published
Lightweight synchronous state management library.
Downloads
2,125
Maintainers
Readme
@known-as-bmf/store
Lightweight synchronous state management library.
Installation
npm install --save @known-as-bmf/store
Description
This library is a reimplementation from scratch of @libre/atom
, that does not rely on global shared objects for internal plumbing and also add some API changes to make it more developer friendly.
It also keeps a state history and provide a way to navigate it.
Some vocabulary:
- store: A wrapper around data that is used to change said data and subscribe to these changes.
- state: The actual data that the store holds. It can be pretty much anything.
Usage
To create a shared store, use of
import { of } from '@known-as-bmf/store';
const store = of({
preferences: { theme: 'dark', lang: 'fr' },
lastOnline: '2020-02-21T18:22:33.343Z',
someArray: [],
});
of
can also be passed a middleware as a second argument (see below).
To read the current state, use deref
import { deref } from '@known-as-bmf/store';
const { preferences } = deref(store);
To update the state, you can use set
or swap
set
totally replaces the current state with the provided value.
import { set } from '@known-as-bmf/store';
set(store, { preferences: {} });
With swap
, you have to provide a function that computes the next state from the current one.
import { swap } from '@known-as-bmf/store';
swap(store, (s) => ({ ...s, lastOnline: new Date().toISOString() }));
We use immer
under the hood, so you can even mutate the state given to you as argument in the update function.
import { swap } from '@known-as-bmf/store';
swap(store, (s) => {
s.preferences.theme = 'light';
s.someArray.push('hurray');
return s;
});
To subscribe to state change, use subscribe
import { subscribe } from '@known-as-bmf/store';
// will be invoked when the state changes
subscribe(store, ({ previous, current }) => console.log(previous, current));
You can also provide a selector function if you're only interested in a subset of the store. The store uses ===
to compare equality.
import { subscribe } from '@known-as-bmf/store';
// will only be invoked when `lastOnline` changes
subscribe(
store,
({ previous, current }) => console.log(previous, current),
(s) => s.lastOnline
);
subscribe
returns an unsubscribe function you can call to stop listening to state changes.
import { subscribe } from '@known-as-bmf/store';
const unsubscribe = subscribe(store, ({ previous, current }) =>
console.log(previous, current)
);
unsubscribe();
Observable
You can create an observable emitting state changes using @known-as-bmf/store-obs
.
Middlewares
You can register a middleware as second argument of of
. If you need to register multiple middlewares, you can use composeMiddlewares
or pipeMiddlewares
to merge them (from right-to-left and left-to-right respectively). Order of middlewares might be important ! They are invoked in order of registration.
Typically, a validation should take place before persisting the state.
Middlewares can use three hooks:
transformState
When the store is asked to change the state, this hook allows the middleware to transform the future state.stateWillChange
Invoked when the state is about to change.stateDidChange
Invoked when the state just changed.
This doc is still in progress but you can look at the typings for more information.
Some pre-existing middlewares:
- @known-as-bmf/store-middleware-validator - validate / reject state updates.
- @known-as-bmf/store-middleware-timetravel - undo / redo state changes.
- @known-as-bmf/store-middleware-persist - persist state to browser storage.
API
of
/**
* Creates a store.
* @param initialState The initial value of the state.
* @param middleware Middleware to use for this store. You can compose multiple
* middlewares with `composeMiddlewares` and `pipeMiddlewares`.
*/
function of<S>(initialState: S, middleware?: Middleware<S>): Store<S>;
type Middleware<S> = (store: Store<S>, hooks: Hooks<S>) => void;
interface Hooks<S> {
/**
* Register a function that will be invoked each time a state change is requested.
* This function can transform the state and must return an array containing the new state.
* An array must be returned because of implementation details behind the scene.
* @param fn A state transformation function.
*/
transformState(fn: (state: Readonly<S>) => [S]): () => void;
/**
* Register a function that will be invoked when the state is about to change.
* It is invoked after all `transformState` hooks.
* @param fn A function invoked when the state is about to change.
*/
stateWillChange(fn: (state: Readonly<S>) => void): () => void;
/**
* Register a function that will be invoked after the state changed.
* It is invoked after all `transformState` and `stateWillChange` hooks.
* @param fn A function invoked when the state has changed.
*/
stateDidChange(fn: (state: Readonly<S>) => void): () => void;
}
deref
/**
* Returns the current state of a store.
* @param store The store you want to get the current state from.
* @throws {TypeError} if the store is not a correct `Store` instance.
*/
function deref<S>(store: Store<S>): S;
swap
/**
* Changes the state of a store using a function.
* @param store The store of which you want to change the state.
* @param mutationFn The function used to compute the value of the future state.
* @throws {TypeError} if the store is not a correct `Store` instance.
* @throws {Error} if the new state does not pass validation.
*/
function swap<S>(store: Store<S>, mutationFn: (current: S) => S): void;
set
/**
* Changes the state of a store with a new one.
* @param store The store of which you want to change the state.
* @param newState The new state.
* @throws {TypeError} if the store is not a correct `Store` instance.
* @throws {Error} if the new state does not pass validation.
*/
function set<S>(store: Store<S>, newState: S): void;
subscribe
/**
* Subscribes to state changes.
* @param store The store you want to subscribe to.
* @param callback The function to call when the state changes.
* @returns An unsubscribe function for this specific subscription.
* @throws {TypeError} if the store is not a correct `Store` instance.
*/
function subscribe<S>(
store: Store<S>,
callback: SubscriptionCallback<S>
): () => void;
/**
* Subscribes to state changes.
* @param store The store you want to subscribe to.
* @param callback The function to call when the state changes.
* @param selector The selector function, narrowing down the part of the state you want to subscribe to.
* @returns An unsubscribe function for this specific subscription.
* @throws {TypeError} if the store is not a correct `Store` instance.
*/
function subscribe<S, R>(
store: Store<S>,
callback: SubscriptionCallback<S>,
selector: Selector<S, R>
): () => void;
type SubscriptionCallback<S> = (event: StateChangedEvent<S>) => void;
type Selector<S, R> = (state: S) => R;
interface StateChangedEvent<S> {
/**
* The previous value of the state.
*/
previous: S;
/**
* The new value of the state.
*/
current: S;
}