@musicq/store
v2.1.0
Published
A state manager built on top of RxJS.
Downloads
13
Readme
Store
Store is a state manager library which is built on top of
RxJS
. It's inspired by
redux-observable
, but
doesn't depend on redux.
Philosophy
dispatch monitor
view ---------------> action <----------------> side effects (e.g. async tasks)
∧ |
| |
| |
| subscribe |
| |
| |
| ∨
store <-------------- reducer
update
Installation
Using npm
npm install @musicq/store
or using yarn
yarn add @musciq/store
Usage
First of all, we need to create a global store instance using createStore
function.
Create a store.js file and append the below code.
import { createStore } from '@musicq/store'
export const store = createStore()
It will create a store instance. The store instance contains 2 objects, one is
state$
, which is the source of our state, the other is dispatch
function, it
is used to dispatch an action to update our state.
Next we need to create a reducer to reduce our state.
Create a file todo.js
export function todos(state = [], action) {
switch (action.type) {
case 'CREATE TODO':
return [...state, action.payload]
case 'DELETE TODO':
return state.filter((t) => t.id !== action.payload)
default:
return state
}
}
Then, import todo reducer into store.js
file
import { todos } from './todo'
Pass todo reducer as a parameter of createStore
export const store = createStore(todos)
Everything is ready, let's play with it!
Create a app.js
file.
import { store } from 'store'
store.state$.subscribe((state) => console.log(state))
store.dispatch({
type: 'CREATE TODO',
payload: { id: 1, content: 'Learn how to use @musicq/store' },
})
setTimeout(() => {
store.dispatch({ type: 'DELETE TODO', payload: 1 })
}, 3000)
That looks great! However, how could we dispatch an async action? That's super
easy in store
.
Let's say we need to tell the server the new todo data via HTTP request, then update the store once the server responses.
To achieve that goal, let's create an effects.js
file to manage our effect
functions.
import { createEffect } from '@musicq/store'
import { switchMap } from 'rxjs/operators'
import { from } from 'rxjs'
export const effect = createEffect((action$, state$, dispatch) =>
action$.pipe(
filter((action) => action.type === 'SEND NEW TODO TO SERVER'),
switchMap((action) =>
// mock communication with server
from(
new Promise((resolve) => {
setTimeout(() => resolve(action.payload), 1000)
})
)
),
// Dispatch an action
// This equals to `dispatch({type: 'CREATE TODO', payload: res})`
map((res) => ({ type: 'CREATE TODO', payload: res }))
)
)
Now, let's import our effects into store.js
file and register it to the store
to make it work.
import { effect } from './effects.js'
store.registerEffects([effect /* you can have multiple effects here */])
Then let change the dispatch action name in our app.js
file
store.dispatch({
type: 'SEND NEW TODO TO SERVER',
payload: { id: 1, content: 'Learn how to use @musicq/store' },
})
Now when we run the app.js
, it won't update the store immediately, instead,
there will be a 1s delay, and then update the store.
So the flow will be
dispatch create action --- 1s later update the store --------- 3s later delete the todo from the store ---->
Types
Action
interface Action {
type: string
payload?: any
}
Reducer
type Reducer<T> = (state: T, action: Action) => T
EffectFn
export type EffectFn<T> = (
action$: Subject<Action>,
state$: Observable<T> & { value: T },
dispatch: DispatchFn
) => Observable<Action | any>
API
createStore
To create a store instance
type createStore = <T>(reducers?: Reducer<T>) => {
state$: Observable<T> & { value: T }
dispatch: (action: Action) => void
registerEffects: (effects: EffectFn<T>[]) => void
}
createEffect
type createEffect = <T>(
fn: EffectFn<T>
) => (
action$: Subject<Action>,
state$: Observable<T> & { value: T },
dispatch: DispatchFn
) => Observable<any>
combineReducers
Combine multiple reducers
type combineReducers = (reducers: {
[key: string]: Reducer<any>
}) => Reducer<any>
For example, if you have a todo
reducer and a user
reducer. You can combine
them up and pass it to the createStore
.
function todos(state = [], action) {
switch (action.type) {
case 'CREATE TODO':
return [...state, action.payload]
default:
return state
}
}
function users(state = [], action) {
switch (action.type) {
case 'CREATE USER':
return [...state, action.payload]
default:
return state
}
}
const reducers = combineReducers({
todos,
users,
})
const store = createStore(reducers)