@alcadica/state-manager
TypeScript icon, indicating that this package has built-in type declarations

1.0.3 • Public • Published

@alcadica/state-manager

Coverage Maintainability

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));

Licence

MIT

Package Sidebar

Install

npm i @alcadica/state-manager

Weekly Downloads

3

Version

1.0.3

License

MIT

Unpacked Size

24.7 kB

Total Files

7

Last publish

Collaborators

  • npm-alcadica
  • octod