stama v1.0.1
Stama JS
An asynchronous and flexible state manager/machine.
Installation
yarn add stama
Usage
Simple
import State from 'stama';
class CounterState extends State {
static initState = { count: 0 }
increase = () => this.modState({ count: v => v + 1 })
decrease = () => this.modState({ count: v => v - 1 })
reset: () => this.setState({ count : 0 })
}
const counter = new CounterState();
counter.subscribe(console.log);
// prints { count: 0 }
counter.increase();
// prints { count: 1 }Advanced
import State from 'stama';
class DataState extends State {
static initState = { loading: false, items: [] }
static deriveProps(state) {
let totalValue = 0;
state.items.forEach(item => totalValue += item.value;
return { totalValue };
}
loadData = async url => {
await this.setState({ loading: true });
const data = await fetch(url);
return this.setState({ loading: false, data })
}
}
const state = new DataState();
state.subscribe(console.log);
// prints { loading: false, data: null, totalValue: 0 }
state.loadData('endpoint/test');
// prints:
// { loading: true, data: null }
// { loading: false, data: [...], totalValue: 12.. }Motivation
There are probably more state managers than there are stars in the sky. Since I like stars, I decided to create another one.
This state manager takes much inspiration from unstated, which in turn mimics the React
way of setState.
Details
State class
The State class is a base class adding state management capabilities to subclasses.
Just as with React state, the state object should not be modified
without calling setState or modState. Therefore the .state property
is read only. However, it is impossible to prevent accidentally modifying the
inner properties of the state. Therefore, the State class offers, next to
setState, a more powerful modState function that makes immutable
state changes a lot easier.
static initState
If the initial state of the subclass doesn't depend on any inputs, it's easiest to define this
static property on the class. The State class will set its initial state to this value,
potentially with derived props if specified.
[static] deriveProps(state)
If any properties need to be derived from the state, it is possible to define a static or instance
method called deriveProps that receives the latest state, and can return an object containing
any derived properties.
class ExampleState extends State {
static initState = { value: 5 }
static deriveProps(state) {
return { squaredValue: state.value * state.value };
}
}It is also possible to define a static or instance property with the same name that contains an object describing for each derived property how it is calculated.
class ExampleState extends State {
static initState = { value: 5 };
static deriveProps = { squaredValue: state => state.value * state.value };
}constructor([initState])
Any subclass of State can pass its initial state by calling super(initState) in its constructor.
This will immediately set the provided initial state, potentially with derived props if specified.
.state
Returns the current state of a State instance.
.stateStream
Returns an RxJS Observable emitting every new state.
.subscribe(onNext, [onError], [onComplete])
Convenience method for .stateStream.subscribe(...)
setState(updater, [callback]) (Promise)
Updates the current state according to the updater, and then calls the callback function. The updater can be an object describing the new state, which then will be directly set. Or, it can be an update function taking the current state and returning a new one.
setState returns a Promise that resolves once the state has been updated,
the callback has been called, and the stateDidChange method has been called.
modState(updater, [callback]) (Promise)
Modifies the current state according to the updater, and then calls the callback function. The updater can be an object describing how the state should be modified.
modState is a powerful way to modify parts of existing state. Here are some examples:
class ExampleState extends State {
static initState = {
value: 5,
foo: {
bar: 12,
other: 33
}
}
increaseValue = () => this.modState({ value: v => v + 1 })
increaseBar = () => this.modState({ foo: { bar: v => v + 1 })
addValueToBar = () => this.modState({ foo: { bar: (v, state) => v + state.value } })
addBarToValue = () => this.modState({ value: (v, state) => v + state.foo.bar })
addBarToOther = () => this.modState({ foo: { other: (v, _, foo) => v + foo.bar } })
changeAll = () => this.modState({
value: (v, state) => v + state.foo.bar,
foo: {
bar: 43,
other: (v, _, foo) => v + foo.bar
}
})
}modState returns a Promise that resolves once the state has been updated,
the callback has been called, and the stateDidChange method has been called.
stateDidChange(newState, oldState)
A utility method that is called on every state change. It's probably not wise to change state in this method.
.forceStateUpdate()
Sometimes it is unavoidable to have objects in the state that update themselves.
If these changes need to retrigger some state calculation in the current State class,
then forceStateUpdate should be called everytime any relevant object changes it state.
Basically, it does not change the direct state, but since nested objects may have changed
it will again apply deriveState on the current state, which may get updated
property values.
.clone()
Creates a new instance of the current State class with the exact same state.
7 years ago