Insula is an event-driven state management library for JavaScript applications.
npm install --save insula
yarn add insula
To create a new store initialize a new Store
object by passing in the initial state.
import Store from 'insula';
const initialState = {foo: 'bar'};
const store = new Store(initialState);
There are two ways to access values from the store. getState
returns the entire state value, and getPartialState
returns a portion of the state described by the passed selector.
const store = new Store({
nested: {
object: 'value'
}
});
store.getState(); // {nested: {object: "value"}}
store.getPartialState(['nested']); // {object: "value"}
store.getPartialState(['nested', 'object']); // "value"
Note that accessing any non-existant part of state will return a null
value.
const store = new Store({});
store.getPartialState(['some', 'nonexistant', 'object']); // null
A Store
has two ways to update its values. setState
replaces the entire store value with a new object while setPartialState
updates only a portion of state specified by a selector.
const store = new Store({foo: 'bar'});
store.setState({fiz: 'biz'});
store.getState(); // {fiz: 'biz'}
store.setPartialState(['foo'], 'bar');
store.getState(); // {fix: 'biz', foo: 'bar'}
Note that setting a part of state whose object chain does not exist will create the necessary objects inside state.
const store = new Store({});
store.setPartialState(['nested', 'object'], 'value');
store.getState(); // {nested: {object: "value"}}
A Store
allows subscriber functions to be added for all or part of state. By using selectors to explcitly declare what part of state to subscribe to, these functions are only called when relevant pieces of state are changed.
The subscribeToState
method takes two arguments, the first is an array of selectors and the second is the subscriber function. When called, the subscriber is passed an array of values in response to the selectors.
const store = new Store({
nested: {object: 'value'},
foo: 'bar'
});
const selectors = [
['nested', 'object'],
['foo']
];
function subscriber(values) {
// values is an array with the state values contained in `nested.object` and `foo`
const [nestedObject, foo] = values;
};
store.subscribeToState(selectors, subscriber);
store.setPartialState(['foo'], 'baz');
// subscriber will be called with "value" and "baz" as the two values
A subscriber will be called if any part of its selector overlaps with a state change. Take a look at this example to understand what changes will affect a subscriber.
store.subscribeToState([['nested', 'object']], ([nestedObject]) => {});
// these updates will trigger the subscriber
store.setState({}); // changing the entire state will affect any subscriber
store.setPartialState(['nested'], {}); // the new value for `nested` could change `nested.object`
store.setPartialState(['nested', 'object'], {}); // this matches the subscriber
store.setPartialState(['nested', 'object', 'subobject'], {}); // a piece of state contained by the selector changed
// these will not trigger the subscriber
store.setPartialState(['somewhere-else'], {}); // `somewhere-else` doesn't match the selector at all
store.setPartialState(['nested', 'someOtherObject'], {}); // `nested.object` doesn't care about `nested.someOtherObject`
The subscribeToState
method returns a function that will remove the subscription.
const store = new Store({});
// add a subscription
const unsubscribe = store.subscribeToState([['foo']], () => {});
// remove the subscription
unsubscribe();
Insula encourages state updates to be driven by events. The Store
provides on
and off
methods to add and remove event listeners as well as a dispatch
method to send events and payloads. Event listeners are called with the event payload and an object with additional functions for interacting with the state. These listeners can optionally dispatch other events, retrieve state values, or update state values.
const store = new Store({});
store.on(
'MY_EVENT',
(payload, {dispatch, getState, getPartialState, setState, setPartialState}) => {
// ...
}
);
store.dispatch('MY_EVENT', {the: 'payload'});
A selector
is an array of string values describing how to access a part of state. To set or get a state value the array values are used to traverse the state object. Using selectors allows Insula to understand which state subscription functions are affected by a state update.
Using arrays to describe the selectors allows Insula to support any kind of data structure, including when object keys include the .
character. There is a middleware if you'd rather use strings such as nested.object
for your selectors.
Performance is a major goal for Insula and is achieved primarily through two mechanisms. Selectors allow Insula to understand the scope of a change and call only those functions which have subscribed to an affected part of state.
The second key element to Insula's performance is subscriber batching. While calls to setState
and setPartialState
are synchronous (you can get call getState
/getPartialState
right away and retrieve the new state values), any affected subscribers will not be called until the next event loop cycle.
const store = new Store({});
const subscriber = () => console.log('subscriber called'); // called only once in this example
store.subscribeToState([[]], subscriber); // empty selector subscribes to all state changes
store.setState({});
store.setPartialState(['nested'], {});
store.setPartialState(['foo'], 'bar');
store.setPartialState(['foo'], 'baz');
store.setState({something: 'else'});