@alcadica/state-manager
v1.0.3
Published
A simple modular state manager for your applications
Downloads
25
Readme
@alcadica/state-manager
A hackable state manager which lets you write a portion or the whole state of an application.
Installation
npm install --save @alcadica/state-manager
API
create
This is the default export. This function creates a new store with a initial state.
The returning value is an instance of StoreManager.
Example:
import create from '@alcadica/state-manager';
const store = create({
foo: 100,
bar: 'baz',
hello: 'world'
});
In typescript the type of the state is inferred automatically, but you can provide the state a type if you prefer.
Example:
import create from '@alcadica/state-manager';
interface MyStore {
foo: number;
bar: string;
hello: string;
someOtherStuff?: string;
}
const store = create<MyStore>({
foo: 100,
bar: 'baz',
hello: 'world'
});
createAction
Actions are the only way to communicate with the reducer, and are used to update the state.
They consist in a function which returns an object with a type
and a payload
. You can quickly create an action using a store createAction
method, or the stand-alone createAction
function.
import create, { createAction } from '@alcadica/state-manager';
interface MyStore {
foo: number;
bar: string;
hello: string;
someOtherStuff?: string;
}
const store = create<MyStore>({
foo: 100,
bar: 'baz',
hello: 'world'
});
const incrementFoo = store.createAction<number>('incrementFoo');
// or
const incrementFooToo = createAction<number>('incrementFoo');
export const actions = {
incrementFoo
}
export default store;
Actions can be dispatched by the store with the dispatch
method.
import store, { actions } from './store';
store.dispatch(actions.increment(100));
In order to update the state, you have to subscribe a callback to the reducer
signal.
import create, { createAction } from '@alcadica/state-manager';
interface MyStore {
foo: number;
bar: string;
hello: string;
someOtherStuff?: string;
}
const store = create<MyStore>({
foo: 100,
bar: 'baz',
hello: 'world'
});
const incrementFoo = store.createAction<number>('incrementFoo');
// or
const incrementFooToo = createAction<number>('incrementFoo');
store.reduce.connect((state, action, update) => {
switch(action.type) {
case incrementFoo.type:
case incrementFooToo.type:
update({ foo: state.foo + action.payload });
break;
}
});
export const actions = {
incrementFoo
}
export default store;
Note that merges stores will react to the same action types.
merge
Merge combines two or more stores in one.
// actions.ts
import { createAction } from '@alcadica/state-manager';
export const increment = createAction<number>('increment');
export default {
increment
}
// store1.ts
import actions from './actions';
import create from '@alcadica/state-manager';
const store = create({
foo: 100
});
store.reducer.connect((state, action, update) => {
if (action.type === actions.increment.type) {
update({ foo: state.foo + action.payload })
}
});
export default store;
// store2.ts
import actions from './actions';
import create from '@alcadica/state-manager';
const store = create({
bar: 200
});
store.reducer.connect((state, action, update) => {
if (action.type === actions.increment.type) {
update({ bar: state.bar + action.payload })
}
});
export default store;
// main-store.ts
import { merge } from '@alcadica/state-manager';
import actions from './actions';
import store1 from './store1';
import store2 from './store2';
const mainStore = merge({ store1, store2 });
mainStore.getState() // { store1: { foo: 100 }, store2: { bar: 200 } }
mainStore.dispatch(actions.increment(1));
mainStore.getState() // { store1: { foo: 101 }, store2: { bar: 201 } }
StoreManager
StoreManager is the object which is returned by the create
default export. Do not use it unless you want to extend it.
Example
import { StoreManager } from '@alcadica/state-manager';
export class MyExtendedStoreManager<T> extends StoreManager<T> {
}
export default function create<T>(initialState: T = <T> {}): MyExtendedStoreManager<T> {
return new MyExtendedStoreManager<T>(initialState);
}
Examples
Todo app
// numbers.ts
import createStore from '@alcadica/state-manager';
export enum TodoFilter {
Completed,
Uncompleted
}
export const store = createStore({
currentTodo: '',
filter: undefined,
todos: []
});
export const addTodo = createStore.createAction('addTodo');
export const filter = createStore.createAction<TodoFilter | undefined>('filter');
export const edit = createStore.createAction<string>('edit');
export const toggleTodo = createStore.createAction<number>('toggleTodo');
export const removeTodo = createStore.createAction<number>('removeTodo');
store.reducer.connect((state, action, update) => {
switch (action.type) {
case addTodo.type:
update({
todos: [
... state.todos,
{ label: action.payload, status: TodoFilter.Uncompleted }
]
});
break;
case edit.type:
update({ currentTodo: action.payload });
break;
case filter.type:
update({ filter: action.payload });
break;
case removeTodo.type:
update({ todos: state.todos.filter((_, index) => index !== action.payload) });
break;
case toggleTodo.type:
update({
todos: state.todos.map((todo, index) => {
if (index === action.payload) {
todo.completed = !todo.completed;
}
return todo;
})
});
break;
}
});
export default {
TodoFilter,
addTodo,
filter,
edit,
toggleTodo,
removeTodo,
store
}
// component.tsx
import { connect } from '@alcadica/state-manager/react';
import numbers from './numbers';
function MyCounterComponent(props: any): JSX.Element {
const { store, TodoFilter, ... actions } = numbers;
const todos = props.todos;
return (
<div>
<select onChange={event => store.dispatch(actions.filter(event.target.value))}>
<option>All</option>
<option value={TodoFilter.Completed}>Completed</option>
<option value={TodoFilter.Uncompleted}>Uncompleted</option>
</select>
<ul>
{
props.todos.filter(todo => props.filter ? todo.status : true).map((todo, index) =>
<li
onClick={() => store.dispatch(actions.toggleTodo(index))}
style={{ textDecoration: todo.completed ? 'line-through' : undefined }}
>
{todo.label}
</li>
)
}
</ul>
<div>
<label>todo:</label>
<input onChange={event => store.dispatch(action.edit(event.target.value))} type="text" value={props.currentTodo}/>
<button onClick={() => store.dispatch(actions.addTodo())}>Create todo</button>
</div>
</div>
)
}
export const MyCounter = connect(numbers.store)(MyCounterComponent);
Composing smaller stores into a big global state
// actions.ts
import createStore from '@alcadica/state-manager';
export const decrement = createStore.createAction<string>('decrement');
export const increment = createStore.createAction<string>('increment');
export const updateString = createStore.createAction<string>('updateString');
export default {
decrement,
increment,
updateString,
}
// store1.ts
import createStore from '@alcadica/state-manager';
import actions from './actions';
export const store1 = createStore({
hello: 'world'
});
store1.reducer.connect((state, action, update) => {
switch (action.type) {
case actions.updateString.name:
update({ hello: action.payload });
break;
}
});
// store2.ts
import createStore from '@alcadica/state-manager';
import actions from './actions';
export const store2 = createStore({
value: 0
});
store2.reducer.connect((state, action, update) => {
switch (action.type) {
case actions.decrement.name:
update({ value: state.value - 1 });
break;
case actions.increment.name:
update({ value: state.value + 1 });
break;
}
});
// global-state.ts
import createStore from '@alcadica/state-manager';
import * as store1 from './store1';
import * as store2 from './store2';
import actions from './actions';
export const globalStore = createStore.merge({
store1: store1.store,
store2: store2.store,
});
globalStore.dispatch(actions.increment());
console.log(globalStore.getState().store2.value) // 1
globalStore.dispatch(actions.increment());
console.log(globalStore.getState().store2.value) // 2
globalStore.dispatch(actions.decrement());
console.log(globalStore.getState().store2.value) // 1
globalStore.dispatch(actions.updateString('bar'));
console.log(globalStore.getState().store1.hello) // 'bar'
Using middlewares
import createStore from '@alcadica/state-manager';
export const store = createStore();
store.use(store => state => action => console.log(action));