oj-store
TypeScript icon, indicating that this package has built-in type declarations

1.0.8 • Public • Published

Store

A complete state / store solution Get the benefits of immutable data with the ease of mutable data. History (undo / redo, persistent) support with deltas (only the diffs are stored). Support for async (Promise) reducers. Subscribe to store events.

Usage

import

import { Store } from "oj-store";

Initialize

const initialState = {
  name: "Orange Juice",
  deep: { nested: [{ data: "is easy" }] }
}
const store = new Store(initialState)

Reducers

const reducers = {
  setName: (name: string) => draft => { draft.name = name },
  pushDeepNested: (data: { data: string }) => draft => { draft.deep.nested.push(data) },
  setNameFromApi: () => 
    fetch("url/user")
      .then(x => x.json())
      .then(x => draft => { draft.name = x.name})
}
const store = new Store(initialState, { reducers })

History

The history option can be true, false or an object with a getter and setter. You can use the getter and setter for persistency, for example with the localForage package.

const history = {
  set: deltas => localForage.setItem("history", deltas),
  get: () => localForage.getItem("history")
}
const store = new Store(initialState, { history, reducers })

Middleware

const middleware = [
    state => next => reducer => {
      const s = state()
      const nextState = reducer(s)
      console.log("logger", s, nextState)
      next(() => nextState)
    }
]

const store = new Store(initialState, { history, reducers, middleware })

Get state

const current = store.state()
/*
{
  name: "Orange Juice",
  deep: { nested: [ { data: "is easy" } ] }
}
*/

Dispatch reducer

store.dispatch.setName("Awesome")
store.dispatch.pushDeepNested({ data: "super easy" })
/*
{
  name: "Awesome",
  deep: { nested: [{ data: "is easy" }, { data: "super easy" }] }
}
*/

Reduce

store.reduce(d => { d.name = "Awesome" })
store.reduce(d => { d.deep.nested.push({ data: "super easy" }) })
store.reduce(d => ({ name: "Reset", deep: { nested: [] })) // set full state

console.log(store.state())
/*
{
  name: "Awesome",
  deep: { nested: [{ data: "is easy" }, { data: "super easy" }] }
}
*/

Undo / Redo

check if undo/redo is possible

  button.classList.toggle("disabled", !store.canUndo())
  button.classList.toggle("disabled", !store.canRedo())

undo / redo

  store.undo() // undo last change

  store.redo() // redo last undo

  store.undo(4) // undo last 4 changes

Events

Uses the oj-eventaggregator package for handling Events

Change

The change event is emitted after every state change caused by store.reduce(...), store.dispatchreducer, store.undo(...) or store.redo(...) actions.

store.on("change", getState => {
  console.log(getState())
})

Undo

The undo event is emitted after store.undo() changed the state.

store.on("undo", getState => {
  console.log(getState())
})

Redo

The redo event is emitted after store.redo() changed the state.

store.on("redo", getState => {
  console.log(getState())
})

Load

The load event is emitted when the store is initialized and the history is fully restored (if any).

store.on("change", getState => {
  console.log(getState())
})

Types

Store

Store<T, R>

T is the initial state object R is the store reducers object

IReducer

(...args: any[]) => (draft: Draft<T>) => void | Draft<T>

IReducerAsync

(...args: any[]) => Promise<(draft: Draft<T>) => void | Draft<T>>

IReducers<R, T>

{ [k: string]: IReducer<T> | IReducerAsync<T> }

IMiddleware

(state: () => T) => (next: (state: T) => void) => (reducer: any) => any

IStoreOptionsHistory

{
  set: (deltas: Delta[]) => any,
  get: () => Promise<Delta[]> | Delta[] | void,
  ready?: (getState: () => T) => any
}

IStoreOptions<R, T>

{
  history?: boolean | IStoreOptionsHistory<T>
  reducers?: IReducers<R, T>,
  middleware?: IMiddleware<T>[]
}

Package Sidebar

Install

npm i oj-store

Weekly Downloads

7

Version

1.0.8

License

MIT

Unpacked Size

25.6 kB

Total Files

6

Last publish

Collaborators

  • orange-juice