Git Product home page Git Product logo

smitty's Introduction

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.

smitty's People

Contributors

danhayden avatar oksas avatar seveves avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

smitty's Issues

Codepen demos broke

It looks like the Codepen demos are linking to an old URL and should point to the umd build on unpkg. I can create anonymous pens with the proper links and create a PR if you'd like? I noticed V2 is in process though, so maybe I'll wait :)

Wildcard reducer arguments

This is a bug or a feature?

import { createStore } from 'smitty'

const store = createStore(0)

// I know it's a strange use of reducer but it's not forbidden to add
store.addReducer({
  '*': (state, payload) => {
    console.log(state, payload)
    // mitt have a different signature for wildcard's handlers - (type, e)
  },
  'add': (state, payload) => state + payload
})

store.emit('add', 1)
// expected: 0 1
// actual: 1 'add'

don't force state to be an object

There are use cases for intiial state as things like Immutable objects, Maps, etc..., forcing {} makes api smitty flexible, I'll be happy to PR if you agree.

[Advice needed] on triggering re-render of components when state changes

I'm loving emitter based state management via smitty so far. My biggest roadblock at the moment is that I can't simply do something like

<Component amount={amount.state.value} />

and expect Component to update when value changes, like we could via redux props. At the moment I need to use state and add a lot of amount.on(SOMETHING) listeners that then call functions to update that state hence re-rendering component with new values. Furthermore I need to remember to always do amount.off during un-mounting.

This brings me to reason behind this question. Can you advise what could be done here in order to achieve this i.e. something like mapStateToProps but using smitty?

Usage with spread opperator

Hi,

I've tried to use spread operator as opposed to object assign in my reducer, however it doesn't seem to work:

router.addReducer({
  'router/SET_UP': (state, e) => ({
    ...state,
    transtionTo: e.transitionTo
  })
});

yet

router.addReducer({
  'router/SET_UP': (state, e) => {
    return Object.assign({}, state, { count: state.transitionTo + e.transitionTo });
  }
});

not entirely sure why. In terms of babel presets I was running redux with spread operators no problem before.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.