npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

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

Licence

MIT