Tiny flux implementation built on mitt
- Install
- Basic Usage
- Demos
- Usage with Preact and React
- API
- Store
- Action Creator Detailed Example
- Class As Reducer
npm install -S smitty
import { createStore } from 'smitty'
// Create a store with initial state
const initialState = { count: 0 }
const store = createStore(initialState)
store.createActions({
add: 'count/ADD'
})
// add a reducer
store.handleActions({
[store.actions.add]: (state, e, type) => {
// increment foos by amount
return Object.assign({}, state, { count: state.count e.amount })
},
'*': (state, e, type) => {
// '*' can be used for all kinds of fun stuff
console.log(e, type)
if (type === 'count/ADD') {
//...do something
}
return state
}
})
store.actions.add({ amount: 5 })
console.log(store.state) // logs `{ count: 5 }`
- Photo Booth Demonstrates async api and saving parts of the store with localforage
- Basic
- Async
- Fun
-
Preact bindings - preact-smitty
npm install preact-smitty
-
React bindings - react-smitty
npm install react-smitty
initialState: any
required: Determines the shape and initial state of your store. Can be of any type that you choose.
Store: Store
Store
arguments
type: (string | function)
-
[string],
type
determines which reducers are called.const store = createStore(0) store.handleActions({ add: function (state, payload) { return state payload } }) console.log(store.state) // logs 0 store.emit('add', 1) console.log(store.state) // logs 1
-
[function]
type
becomes an action creator that is passed 1 argument- store: Store
This is useful to emit multiple actions from a single emit call.
const store = createStore(0) store.handleActions({ add: function (state, payload) { return state payload } }) function asyncAction (emit, state) { emit('add', 1) console.log(state) // logs 1 setTimeout(() => { emit('add', 1) console.log(state) // logs 3 }, 100) emit('add', 1) console.log(state) // logs 2 } ```
payload: (any) optional
payload to pass to your reducer
const store = createStore({ name: 'Arrow' })
store.handleActions({
'update/NAME': function (state, payload) {
// I really don't care if you return a new state
// Nobody is judging. Do what your ❤️ tells you.
// Just be consistent
return Object.assign({}, state, payload)
}
})
console.log(store.state) // logs { name: 'Arrow' }
store.emit('update/NAME', { name: 'River' })
console.log(store.state) // logs { name: 'River' }
arguments
actionMap: (object)
Object where key is the action creator's name and the value can be of type string
or function
.
If the value is a string
, an action creator is attached to store.actions
as a function that accepts one argument, payload
.
store.createActions({
add: 'count/ADD'
})
// The following are functionally equivalent
store.actions.add(1)
store.emit('count/ADD', 1)
Action creators with a string value can be used as the key in your actionMap
in handleActions
.
store.createActions({
add: 'count/ADD'
})
// add a reducer
store.handleActions({
[store.actions.add]: (state, e, type) => {
// increment foos by amount
return Object.assign({}, state, { count: state.count e.amount })
}
})
store.actions.add({ amount: 5 })
console.log(store.state) // logs `{ count: 5 }`
If the value is a function
, it must be a function that returns an action creator. For async action creators.
store.createActions({
add: (amount) => {
return (store) => {
setTimeout(() => {
store.emit('count/ADD', amount)
}, 16)
}
}
})
store.actions.add(1)
arguments
handlerMap: (object)
Object with keys that correspond to action types passed to emit
When an event is emitted and the key matches the type the reducer is invoked with 3 arguments.
- state: (any) the store's state getter
- payload (any) the payload that was emitted
- type (string) the type that was emitted
const store = createStore({ color: 'blue', hovered: false })
store.handleActions({
'merge': function (state, payload) {
return Object.assign({}, state, payload)
},
'overwrite': function (state, payload) {
return payload
},
// Could do the same in one
// If you really miss redux do this and put a switch statement
'*': function(state, payload, type) {
return type === 'merge' ? Object.assign({}, state, payload) : payload
}
})
console.log(store.state) // logs { color: 'blue', hovered: false }
store.emit('merge', { color: 'red' })
console.log(store.state) // { color: 'red', hovered: false }
store.emit('overwrite', { color: 'green', hovered: true, highlighted: false })
console.log(store.state) // { color: 'green', hovered: true, highlighted: false
Map of all the actions created in store.createActions
This is convenient so that you do not have to deal with action imports across your app.
Convenience shortcut for mitt.on.
Convenience shortcut for mitt.off.
You can pass a function to emit
in order to create an action creator
import { createStore } from 'smitty'
// Create a store with initial state
const initialState = {}
const store = createStore(initialState)
// add our reducer
store.handleActions({
'api/GET_ROOM': (state, { id, res }) => {
return {
...state,
[id]: {
...state[id],
...res.data
}
}
}
})
// create our action creators
const actions = {
requestRoom (id) {
return async (emit, state) => {
emit('REQUEST_ROOM', { id, res: { data: { id } } })
const res = await window.fetch(`https://api.mysite.com/${id}`)
res.data = await res.json()
emit('REQUEST_ROOM', { id, res })
}
}
}
// When calling emit with a function argument, the function will be called with `emit` and `state` as arguments
const result = store.emit(actions.requestRoom('1a'))
// Return whatever you like from your action creator
console.log(result) // logs "Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}"
// After the fetch call, `REQUEST_ROOM` is fired a second time with our response data
result.then(() => console.log(store.state)) // logs `{ 1a: { id: '1a', title: 'My Room' }``
Reducers are iterated with for (let type in reducer) {...}
with no obj.hasOwnProperty
check so this works.
const store = createStore({ foo: 5 })
class HistoryReducer {
constructor (initialHistory = []) {
this.history = createStore(initialHistory)
this.history.handleActions({
update: (state, e) => {
state.push(e)
}
})
}
onUpdate (state, e, type) {
this.history.emit('update', { state, e, type })
}
}
HistoryReducer.prototype['foo/ADD'] = function (state, e, type) {
state.foo = e.foo
this.onUpdate(state, e, type)
}
const historyReducer = new HistoryReducer([])
store.handleActions(historyReducer)
store.emit('foo/ADD', { foo: 5 })
console.log(store.state.foo) // logs 10
store.emit('foo/ADD', { foo: 7 })
console.log(store.state.foo) // logs 17
console.log(historyReducer.history.state)
// logs
// [
// { state: { foo: 10 }, e: { foo: 5 }, type: 'foo/ADD' },
// { state: { foo: 17 }, e: { foo: 7 }, type: 'foo/ADD' }
// ]