Cyclone is an application state management library.
Here is a simple counter.
import { createStore, none } from '@ushiboy/cyclone';
const initialState = { count: 0 };
const update = (state, action) => {
switch (action.type) {
case 'increment': {
return [{ count: state.count + 1 }, none()];
}
case 'decrement': {
return [{ count: state.count - 1 }, none()];
}
default: {
return [state, none()];
}
}
}
const store = createStore(initialState, update);
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch({ type: 'increment' });
store.dispatch({ type: 'increment' });
store.dispatch({ type: 'decrement' });
- State: the state of your application.
- Action: the instructions for changing state.
- Update: the processing function that receives State and Action and returns the next State and Action.
- Store: it is initialized with State and Update, receives Action and updates State and notifies.
The objects with keys and values.
type State = {
[stateName: string]: any;
};
The objects with type
property.
type Action = {
type: string
};
The function that takes State
and Action
as arguments and returns updated State
and next Action
.
type Update<S, A> = (state: S, action: A) => [S, A | Promise<A> | Array<A | Prmise<A>>];
Notify of change of State
by Action
.
type Store<S, A> = {
dispatch(action: A | Promise<A>): Promise<void>,
getState(): S,
subscribe(listener: () => void): () => void,
unsubscribe(listener: () => void): void
};
It create and return a Store
from the initial state and Update
function.
createStore<S, A>(initialState: S, update: Update<S, A>): Store<S, A>
It define Update
which is responsible for specific elements in State
.
It is used in combination with combine
.
reducer<RS, A>(stateName: string, update: ReducerUpdate<RS, A>): ReducerConfig<RS, A>
reducer<RS, A>(stateName: string, dependencies: string[], update: ReducerUpdate<RS, A>): ReducerConfig<RS, A>
It returns a ReducerConfig
object.
If you specify a list of depending State
element names on the 2nd argument, it can be received after the 3rd argument of the ReducerUpdate
function.
type ReducerConfig<RS, A> = {
stateName: string,
update: ReducerUpdate<RS, A>,
dependencies: string[]
};
type ReducerUpdate<RS, A> = (state: RS, action: A, ...dependencyState: any[]) => [RS, A | Promise<A>];
It receives multiple ReducerConfig
objects, creates and returns a single Update
function.
combine<S, A>(...reducerConfig: ReducerConfig): Update<S, A>;
It is used when there is no next Action
after Update
processing.
none(): Action
Store
instance methods.
It execute Action
against Store
.
dispatch(a: A | Promise<A>): Promise<void>
It returns the current State
of Store
.
getState(): S
It subscribes to the Store
change notification and returns unsubscriber
.
If you execute unsubscriber
, It unsubscribes to the Store
change notification.
subscribe(listener: () => void): () => void
It unsubscribes to the Store
change notification.
unsubscribe(listener: () => void): void
An example where three states are processed individually and one depends on the other two.
import { createStore, combine, reducer, none } from '@ushiboy/cyclone';
const store = createStore({ a: 0, b: 0, c: '' }, combine(
reducer('a', (state, action) => {
switch (action.type) {
case 'a$set': {
return [action.payload, none()];
}
default: {
return [state, none()];
}
}
}),
reducer('b', (state, action) => {
switch (action.type) {
case 'b$set': {
return [action.payload, none()];
}
default: {
return [state, none()];
}
}
}),
// depends on 'a' and 'b'
reducer('c', ['a', 'b'], (state, action, a, b) => {
switch (action.type) {
case 'sum': {
return [`${a + b}`, none()];
}
default: {
return [state, none()];
}
}
})
));
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch({ type: 'a$set', payload: 1 }); // { a: 1, b: 0, c: '' }
store.dispatch({ type: 'b$set', payload: 2 }); // { a: 1, b: 2, c: '' }
store.dispatch({ type: 'sum' }); // { a: 1, b: 2, c: '3' }
An example of waiting for 1 second and erasing the message after displaying the message.
import { createStore, none } from '@ushiboy/cyclone';
const showMessage = () => ({ type: 'show' });
const waitAndClearMessage = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve({ type: 'clear' });
}, 1000);
});
};
const store = createStore({ msg: '' }, (state, action) => {
switch (action.type) {
case 'show': {
return [{ msg: 'hello' }, waitAndClearMessage()]; // chain action
}
case 'clear': {
return [{ msg: '' }, none()];
}
default: {
return [state, none()];
}
}
});
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch(showMessage());
(This feature is experimental. It may change in the future.)
If there is a parameter you want to inject into the action, set it to the 3rd argument of the createStore
method.
const store = createStore(initialState, update, { webApi: {...} });
The action can use it by returning a function that receives the injected parameter.
const fetchAction = () => async ({ webApi }) => {
const data = await webApi.fetch();
return {
type: 'fetch',
payload: {
data
}
};
};
store.dispatch(fetchAction());
Changed subscribe
method to return unsubscriber
.
Removed @babel/polyfill
dependency.
Initial Cyclone release.
MIT