Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

chopped-redux

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

chopped-redux - npm Package Compare versions

Comparing version 1.0.4 to 2.0.1

10

package.json
{
"name": "chopped-redux",
"version": "1.0.4",
"description": "A very small Flux implementation based on @gaearon Redux",
"version": "2.0.1",
"description": "A tiny Flux implementation based on @gaearon Redux",
"keywords": [

@@ -20,3 +20,3 @@ "flux",

"devDependencies": {
"faucet": "0.0.1",
"immutable": "^3.7.4",
"standard": "^4.3.2",

@@ -26,5 +26,5 @@ "tape": "^4.0.0"

"scripts": {
"test": "npm run lint && node test/index.js | faucet",
"lint": "standard ."
"test": "npm run lint && node test/index.js",
"lint": "standard"
}
}

@@ -5,8 +5,27 @@ # Chopped Redux

I recommend you try out [Redux](https://github.com/gaearon/redux) and go through its Github issues as it's a great source of knowledge and inspiration.
The idea here is to provide a minimal, solid Flux (à la Redux) base without the [React](http://facebook.github.io/react/index.html) glue (you have to do that yourself), so it's possible to use this library with anything other than React. [Redux](https://github.com/gaearon/redux) allows you to do this too in 1.0, by [splitting itself](https://github.com/gaearon/redux/issues/230).
The idea here is to provide a minimal, solid Flux base without the [React](http://facebook.github.io/react/index.html) glue (you have to do that yourself).
You should be able to switch from Chopped Redux to Redux and viceversa without changing your flux code (constants, action creators and reducers).
This project follows [SemVer](http://semver.org/). 1.0 doesn't mean it's stable or production-ready.
## Bullet points
Chopped main design goal:
- All methods are first-class. You can freely pass them around without the need for `bind`ing.
Why is Flux Redux-style so nice:
- No singletons
- Action creators and reducers (state-less stores) are pure functions
- All state is kept in a single object, and you choose what that is (Immutable, mori, a plain object)
- Plays well with Universal Javascript
The things you'll miss from Redux here:
- Hot reloading
- Middleware
- Built-in time-travel
This project follows [SemVer](http://semver.org/).
## Install

@@ -22,21 +41,33 @@

- Factory: fluxFactory
- dispatch
- getDispatcher
- getState
- subscribe
- wrapActionCreators
```js
var factory = require('chopped-redux')
```
#### Factory: fluxFactory(reducer[, initialState])
Chopped Redux exports a single factory function that returns an object with three main methods:
- *reducer* `Function|Object` These are your stores
- `dispatch`
- `getState`
- `subscribe`
and two helpers:
- `wrap`
- `replaceState`
I like to call this instance object `flux`, in Redux is called the `store`.
The factory has a single mandatory param which is a `reducer` function.
#### factory(reducer[, initialState, listeners])
- *reducer* `Function`
- *initialState* `Mixed` Anything you want to hold your state in
- Returns `Object` A `flux` instance
- *listeners* `Array` Listener callbacks that subscribe to dispatches
The `reducer` function should have this signature:
The `reducer` function should have the folowwing signature:
```js
function (state, action) {
function (state, action) {
// do something with state depending on the action type,
// ideally generating a fresh new value
// ideally generating a fresh new (immutable) value
return state

@@ -52,37 +83,29 @@ }

If you pass an object with reducer functions, a new function will be created which will map the reduced state of every function to a key on the root state object. Like this:
---
#### #dispatch(action)
- *action* `Object|Function`
Action creators should mostly return a plain object of the `{ type: DO_STUFF }` kind. If you need to do async stuff, return a function instead. This function receives `dispatch` and `getState` as params, so you can then actually `dispatch` the plain object needed for dispatching.
```js
// Your stores
{
foo: [Function],
bar: [Function],
baz: [Function]
}
var asyncActionCreator = function (data) {
return function (dispatch, getState) {
// do your async stuff
// The root state inside the `flux` instance
{
foo: {...} // the reduced state from `stores.foo`
bar: {...} // the reduced state from `stores.bar`
baz: {...} // the reduced state from `stores.baz`
dispatch({
type: STUFF_DONE,
payload: foo
})
}
}
// And you could access those like this
flux.getState().foo
```
#### flux.dispatch(action)
#### #getState()
- *action* `Object`
- Returns `Object` The current state
#### flux.getDispatcher()
#### #subscribe(listener)
- Returns the `dispatch` function bound to the `flux` instance. Literally this: `return this.dispatch.bind(this)`
#### flux.getState()
- Returns `Object` Your state
#### flux.subscribe(listener)
- *listener* `Function` A callback that gets fired after every state update

@@ -93,21 +116,15 @@ - Returns `Function` A function to remove the listener

#### fluxFactory.wrapActionCreators(actionCreators, dispatch)
#### #wrap(methods)
- *actionsCreators* `Object` Your action creators in an object
- *dispatch* `Function` The dispatch function to bind to your action creators. You can get this from `flux.getDispatcher()`
- Returns `Object` Your action creators bound to the dispatcher
- *methods* `Object` An object with your action creators methods
- Returns `Object` The same methods wrapping the dispatcher
This is a helper function, so instead of writing this:
So insted of doing this:
```js
var actionsCreators = {
exampleAction: function (foo) {
return {
type: constants.EXAMPLE_TYPE,
value: foo
}
}
var increment = function () {
return { type: INCREMENT }
}
flux.dispatch(actionCreators.exampleAction('bar'))
flux.dispatch(increment())
```

@@ -118,118 +135,31 @@

```js
var actionsCreators = {
exampleAction: function (foo, dispatch) {
return dispatch({
type: constants.EXAMPLE_TYPE,
value: foo
})
}
}
var actions = flux.wrap({ increment: increment })
var wrap = fluxFactory.wrapActionCreators
var actions = wrap(actionCreators, flux.getDispatcher())
actions.exampleAction('bar') // dispatches!
actions.increment()
```
and have the action automatically dispatched.
The nice thing about this is that you can provide you view components with this wrapped action creator methods which you can call directly without needing the `flux` instance available.
## Example
#### #replaceState(state)
### Stores
- *state* `Mixed` Whatever your state is
Stores are pure functions, just like in Redux. They don't hold the state and don't emit events either (that's why in Redux they're called Reducers; I prefer to keep calling them Stores). They just receive the state, update it, and return the new state.
This will replace the current state reference in your `flux` instance. This could be used for debugging, time-travel. For example, you could keep a copy of your `state` object of a specific point in time, and restore it later.
```js
var actionTypes = require('../constants/action-types')
// Copy of current state
var stateCopy = flux.getState()
var initialState = 0
// Do stuff
module.exports = function (state, action) {
state = state || initialState
switch (action.type) {
case actionTypes.INCREMENT_COUNTER:
return state + 1
break
case actionTypes.DECREMENT_COUNTER:
return state - 1
break
default:
return state
}
}
// Some time later
flux.replaceState(stateCopy)
```
### Actions
---
Action creators are functions that yield an action object (or *payload* in vanilla Flux terminology). They can simply return that object, or if you need to do async operations, you can pass in a dispatch callback and fire it passing in the action object. If the latter is the case, always pass the `dispatch` function as the last argument, so you can use the utility wrapper function (`wrapActionCreators`).
Further reading: [The Evolution of Flux Frameworks](https://medium.com/@dan_abramov/the-evolution-of-flux-frameworks-6c16ad26bb31).
```js
var actionTypes = require('../constants/action-types')
exports.increment = function (dispatch) {
return dispatch({
type: actionTypes.INCREMENT_COUNTER
})
}
exports.decrement = function (dispatch) {
return dispatch({
type: actionTypes.DECREMENT_COUNTER
})
}
```
### Constants (for action types)
Yep.
```js
module.exports = {
INCREMENT_COUNTER: 'INCREMENT_COUNTER',
DECREMENT_COUNTER: 'DECREMENT_COUNTER',
}
```
### Make it work together
```js
var fluxFactory = require('chopped-redux')
var wrap = fluxFactory.wrapActionCreators
// Define stores and action creators
var stores = {
counter: require('./stores/counter')
}
var actionCreators = {
counter: require('./actions/counter')
}
// Create a flux instance passing in the stores and initial state
var flux = fluxFactory(stores, { counter: 1 })
// Bind action creators to the dispatcher
var actions = wrap(actionCreators, flux.getDispatcher())
// Subscribe a callback to state updates
var unsubscribe = flux.subscribe(function () {
console.log(flux.getState())
})
// Trigger an action: this dispatches the action,
// the state tree in the flux instance goes through the store function(s),
// and listener callbacks fire
actions.increment()
// => { counter: 2 }
unsubscribe()
```
## License
MIT
var mapValues = require('./map-values')
module.exports = function (reducer, state, listeners) {
listeners = listeners || []
var combineReducers = function combineReducers (reducers) {
return function (tree, action) {
tree = tree || {}
if (typeof reducer !== 'function') {
throw new TypeError('The `reducer` param must be a function')
}
return mapValues(reducers, function (fn, key) {
return fn(tree[key], action)
})
function getState () {
return state
}
}
module.exports = function fluxFactory (reducer, initialState) {
if (!reducer) {
throw new Error('The `reducer` param is mandatory')
function replaceState (nextState) {
state = nextState
}
var reduce = typeof reducer === 'function'
? reducer
: combineReducers(reducer)
function dispatch (action) {
if (typeof action === 'function') {
return action(dispatch, getState)
}
return {
reduce: reduce,
action = action || {}
state: initialState !== null ? initialState : Object.create(null),
state = reducer(state, action)
listeners.forEach(function (fn) { fn(action) })
}
listeners: [],
function wrap (methods) {
var wrapped = {}
dispatch: function (action) {
var self = this
self.state = self.reduce(self.state, action)
self.listeners.forEach(function (listener) {
listener(action)
})
},
Object.keys(methods).forEach(function (key) {
if (typeof methods[key] === 'function') {
wrapped[key] = function () {
dispatch(methods[key].apply(null, arguments))
}
}
})
getDispatcher: function () {
var self = this
return self.dispatch.bind(self)
},
return wrapped
}
getState: function () {
var self = this
return self.state
},
function subscribe (fn) {
listeners.push(fn)
subscribe: function (listener) {
var self = this
self.listeners.push(listener)
return function unsubscribe () {
var index = self.listeners.indexOf(listener)
self.listeners.splice(index, 1)
}
return function unsubscribe () {
var index = listeners.indexOf(fn)
listeners.splice(index, 1)
}
}
return {
getState: getState,
replaceState: replaceState,
dispatch: dispatch,
wrap: wrap,
subscribe: subscribe
}
}
module.exports.wrapActionCreators = require('./wrap-action-creators')
var test = require('tape')
var Immutable = require('immutable')
var fluxFactory = require('../')
var wrapActionCreators = fluxFactory.wrapActionCreators
var MANUAL = 'MANUAL'
var stores = {
counter: require('./fixtures/stores/counter'),
sum: require('./fixtures/stores/sum'),
beep: function (state, action) {
return action.type === MANUAL
? 'boop'
: state || undefined
// Silly constants
var INCREMENT_COUNTER = 'INCREMENT_COUNTER'
var DECREMENT_COUNTER = 'DECREMENT_COUNTER'
// Actions
var increment = function () {
return {
type: INCREMENT_COUNTER
}
}
var actionCreators = {
counter: require('./fixtures/actions/counter'),
sum: require('./fixtures/actions/sum')
var decrement = function () {
return {
type: DECREMENT_COUNTER
}
}
/*
var flux = fluxFactory(stores, {})
var dispatch = flux.getDispatcher()
var actions = {
counter: wrapActionCreators(actionCreators.counter, dispatch),
sum: wrapActionCreators(actionCreators.sum, dispatch)
// Initial state
var state = { counter: 1 }
var immutableState = Immutable.Map({ counter: 1 })
// Reducers
var reducer = function (state, action) {
state = state || { counter: 10 }
switch (action.type) {
case INCREMENT_COUNTER:
state.counter++
break
case DECREMENT_COUNTER:
state.counter--
break
}
return state
}
*/
var immutableReducer = function (state, action) {
switch (action.type) {
case INCREMENT_COUNTER:
state = state.set('counter', state.get('counter') + 1)
break
case DECREMENT_COUNTER:
state = state.set('counter', state.get('counter') - 1)
break
}
return state
}
test('fluxFactory', function (t) {
t.plan(6)
test('factory', function (t) {
t.plan(2)
var flux1 = fluxFactory(stores, {})
var flux2 = fluxFactory(stores, {})
t.notEqual(flux1, flux2, 'is a factory (no singletons)')
t.ok(typeof flux1.dispatch === 'function', 'has `dispatch` method')
t.ok(typeof flux1.getDispatcher === 'function', 'has `getDispatcher` method')
t.ok(typeof flux1.getState === 'function', 'has `getState` method')
t.ok(typeof flux1.subscribe === 'function', 'has `subscribe` method')
t.throws(fluxFactory, 'should throw if missing first argument')
var identity = function (x) { return x }
var a = fluxFactory(identity)
var b = fluxFactory(identity)
t.throws(function () {
fluxFactory()
}, 'throws if missing reducer param')
t.notEqual(a, b, 'no singleton')
})
test('flux instance dispatcher', function (t) {
t.plan(5)
test('mutable, listeners', function (t) {
t.plan(4)
var flux = fluxFactory(stores, {})
var dispatch = flux.getDispatcher()
var actions = {
counter: wrapActionCreators(actionCreators.counter, dispatch),
sum: wrapActionCreators(actionCreators.sum, dispatch)
var flux = fluxFactory(reducer, state)
flux.subscribe(function () {})
var unsubscribe = flux.subscribe(function () { t.pass('listener called') })
flux.subscribe(function () {})
flux.dispatch(increment())
t.equal(flux.getState().counter, 2, 'action dispatched 1')
unsubscribe()
flux.dispatch(decrement())
t.equal(flux.getState().counter, 1, 'action dispatched 1')
t.equal(flux.getState(), state, 'state is the same mutable object')
})
test('immutable', function (t) {
t.plan(3)
var flux = fluxFactory(immutableReducer, immutableState)
flux.dispatch(increment())
t.equal(flux.getState().get('counter'), 2, 'action dispatched 1')
flux.dispatch(decrement())
t.equal(flux.getState().get('counter'), 1, 'action dispatched 1')
t.notEqual(flux.getState(), immutableState, 'state is not the same object')
})
test('no initial state provided', function (t) {
t.plan(1)
var flux = fluxFactory(reducer, null)
flux.dispatch(decrement())
t.equal(flux.getState().counter, 9, 'gets set in reducer')
})
test('first-class dispatch and getState, no bind', function (t) {
t.plan(2)
var initialState = { counter: 5 }
var flux = fluxFactory(reducer, initialState)
function wrapper (fn) {
return function (a) {
return fn(a)
}
}
actions.counter.increment()
t.equal(flux.getState().counter, 1, 'dispatches via wrapped action creators (1)')
var dispatch = wrapper(flux.dispatch)
var getState = wrapper(flux.getState)
actions.counter.decrement()
t.equal(flux.getState().counter, 0, 'dispatches via wrapped action creators (2)')
t.equal(getState(), initialState, 'getState')
actionCreators.counter.increment(dispatch)
t.equal(flux.getState().counter, 1, 'dispatches via action creators')
dispatch(increment())
t.equal(getState().counter, 6, 'dispatch')
})
flux.dispatch({ type: MANUAL })
t.equal(flux.getState().beep, 'boop', 'dispatches manually')
test('handle actions being functions', function (t) {
t.plan(2)
actions.sum.sum(10)
t.deepEqual(flux.getState(), {
counter: 1,
beep: 'boop',
sum: 10
}, 'handles the state tree props correctly')
var flux = fluxFactory(reducer, { counter: 32 })
flux.dispatch(function (dispatch, getState) {
t.equal(getState().counter, 32, 'getState gets passed in')
dispatch({ type: INCREMENT_COUNTER })
})
t.equal(flux.getState().counter, 33, 'alright')
})
test('flux instance subscribe method', function (t) {
t.plan(6)
test('replaceState', function (t) {
t.plan(2)
var flux = fluxFactory(stores, {})
var initialState = { counter: -1 }
var flux = fluxFactory(reducer, initialState)
var off1 = flux.subscribe(function (action) { t.pass('fires listener (1) ' + action.type) })
var off2 = flux.subscribe(function (action) { t.pass('fires listener (2) ' + action.type) })
var off3 = flux.subscribe(function (action) { t.pass('fires listener (3) ' + action.type) })
flux.dispatch(increment())
t.equal(flux.getState().counter, 0, '(test dispatch)')
t.ok(typeof off1 === 'function', 'returns ´unsubscribe´ function')
flux.replaceState({ counter: 24 })
flux.dispatch(decrement())
t.equal(flux.getState().counter, 23, 'works')
})
flux.dispatch({ type: 'A' }) // 3 passes
off2()
flux.dispatch({ type: 'B' }) // 2 passes (listener 2 is off)
off1()
off3()
flux.dispatch({ type: 'C' }) // nothing
test('wrap action creators', function (t) {
t.plan(3)
var inc = function (data) {
t.equal(data.foo, 'bar', 'arguments get passed in')
return {
type: INCREMENT_COUNTER
}
}
var flux = fluxFactory(reducer, { counter: 20 })
var actions = flux.wrap({ increment: inc, decrement: decrement })
actions.increment({ foo: 'bar' })
t.equal(flux.getState().counter, 21, 'works')
actions.decrement()
t.equal(flux.getState().counter, 20, 'correctly')
})
test('empty dispatching', function (t) {
t.plan(1)
var flux = fluxFactory(reducer)
t.doesNotThrow(flux.dispatch, 'is possible')
})
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc