oj-store
v1.0.8
Published
store solution with reducers (Repatch), immutability (Immer), and history undo / redo (oj-diff-patch)
Downloads
5
Maintainers
Readme
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>[]
}