Skip to content

tkh44/smitty

Folders and files

NameName
Last commit message
Last commit date

Latest commit

author
Kye Hohenberger
Apr 26, 2017
c9b4bbf · Apr 26, 2017
Apr 24, 2017
Mar 27, 2017
Mar 28, 2017
Mar 23, 2017
Jan 20, 2017
Mar 28, 2017
Mar 23, 2017
Jan 19, 2017
Apr 26, 2017
Apr 24, 2017
Apr 24, 2017
Apr 24, 2017
Mar 16, 2017

Repository files navigation

smitty
smitty

Tiny flux implementation built on mitt

smitty

npm version Build Status codecov

Install

npm install -S smitty

Basic Usage

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 }`

Demos (v2)

Demos (v1)


Usage with Preact and React


API

createStore(initialState: any)

Arguments

initialState: any required: Determines the shape and initial state of your store. Can be of any type that you choose.

Returns

Store: Store Store


Store

emit: (function)

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

    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' }

createActions(): (function)

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)

handleActions(): (function)

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 

actions: (object)

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.

on: (function)

Convenience shortcut for mitt.on.

off: (function)

Convenience shortcut for mitt.off.


Action Creator Detailed Example

You can pass a function to emit in order to create an action creator

running example

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' }``

Class As Reducer

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' }
// ]

Thanks

Thanks to developit for mitt and the project structure.