Skip to content

Commit

Permalink
Initial Commit.
Browse files Browse the repository at this point in the history
combineReducers alternative that supports Immutables.
  • Loading branch information
Asaf Shakarzy committed Sep 4, 2015
1 parent 138e542 commit a654e7d
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 4 deletions.
4 changes: 4 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"stage": 0,
"loose": "all"
}
5 changes: 5 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
lib/*
**/dist/*
**/node_modules/*
**/server.js
**/webpack.config*.js
25 changes: 25 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"extends": "airbnb",
"env": {
"browser": true,
"mocha": true,
"node": true
},
"rules": {
"valid-jsdoc": 2,

"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/react-in-jsx-scope": 2,

// Disable until Flow supports let and const
"no-var": 0,
"vars-on-top": 0,

// Disable comma-dangle unless need to support it
"comma-dangle": 0
},
"plugins": [
"react"
]
}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.DS_Store
*.log
node_modules
dist
lib
coverage
3 changes: 1 addition & 2 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ modification, are permitted provided that the following conditions are met:
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of redux-immutable nor the names of its
* Neither the name of re-notif nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

Expand All @@ -25,4 +25,3 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,33 @@
# redux-immutable
Redux Immutable facilities.
# `redux-immutablejs`

An alternative to [combineReducers](http://rackt.github.io/redux/docs/api/combineReducers.html) that supports
[ImmutableJs](https://facebook.github.io/immutable-js/).

# Setup

## Initial State

Using `combineReducers` it is possible to provide `createStore` with initial state using Immutable [Iterable](https://facebook.github.io/immutable-js/docs/#/Iterable) type, i.e:

```js
import { createStore } from 'redux';
import { combineReducers } from 'redux-immutablejs';

import Immutable from 'immutable';
import * as reducers from './reducers';

const reducer = combineReducers(reducers);
const state = Immutable.fromJS({});

const state = reducer(state);
export default createStore(reducer, state);
```

# FAQ

## How this library is different from 'redux-immutable' ?

This library doesn't dictate any specific reducer structure,
While `redux-immutable` focusing on [CRC](https://github.com/gajus/canonical-reducer-composition), while this library
provides some [convertion middlewares](https://github.com/gajus/redux-immutable/issues/3) from FSA to CCA
and vise versa, if you feel like going with _Redux's vanilla_ is the right approach, then consider using our library.
35 changes: 35 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "redux-immutable",
"version": "0.0.1",
"description": "Redux Immutable facilities",
"scripts": {
"start": "node server.js",
"build:lib": "babel src --out-dir lib"
},
"main": "lib/index.js",
"repository": {
"type": "git",
"url": "https://github.com/indexiatech/redux-immutable.git"
},
"author": "Indexia Tech",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/indexiatech/redux-immutable/issues"
},
"homepage": "http://indexiatech.github.io/redux-immutable",
"peerDependencies": {
"redux": "^2.0.0",
"immutable": "^3.7.5"
},
"devDependencies": {
"babel-core": "^5.6.18",
"babel-loader": "^5.1.4",
"eslint-config-airbnb": "0.0.8",
"eslint-plugin-react": "^3.3.1",
"immutable": "^3.7.5",
"node-libs-browser": "^0.5.2",
"redux": "^2.0.0",
"webpack": "^1.9.11",
"webpack-dev-server": "^1.9.0"
}
}
5 changes: 5 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import combineReducers from './utils/combineReducers';

export {
combineReducers
}
131 changes: 131 additions & 0 deletions src/utils/combineReducers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import Immutable from 'immutable';

// TODO need to find a way to reference Redux's init for compatability
const ActionTypes = { INIT: 'INIT' };
const isImmutable = (obj) => {
return Immutable.Iterable.isIterable(obj);
};

/* eslint-disable no-console */

function getErrorMessage(key, action) {
var actionType = action && action.type;
var actionName = actionType && `"${actionType.toString()}"` || 'an action';

return (
`Reducer "${key}" returned undefined handling ${actionName}. ` +
`To ignore an action, you must explicitly return the previous state.`
);
}

function verifyStateShape(initialState, currentState) {
var reducerKeys = currentState.keySeq();

if (reducerKeys.size === 0) {
console.error(
'Store does not have a valid reducer. Make sure the argument passed ' +
'to combineReducers is an object whose values are reducers.'
);
return;
}

if (!isImmutable(initialState)) {
console.error(
'initialState has unexpected type of "' +
({}).toString.call(initialState).match(/\s([a-z|A-Z]+)/)[1] +
'". Expected initialState to be an instance of Immutable.Iterable with the following ' +
`keys: "${reducerKeys.join('", "')}"`
);
return;
}

const unexpectedKeys = initialState.keySeq().filter(
key => reducerKeys.indexOf(key) < 0
);

if (unexpectedKeys.size > 0) {
console.error(
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" in initialState will be ignored. ` +
`Expected to find one of the known reducer keys instead: "${reducerKeys.join('", "')}"`
);
}
}

/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/

export default function combineReducers(reducers) {
reducers = isImmutable(reducers) ? reducers : Immutable.fromJS(reducers);
const finalReducers = reducers.filter(v => typeof v === 'function');

finalReducers.forEach((reducer, key) => {
if (typeof reducer(undefined, { type: ActionTypes.INIT }) === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined.`
);
}

var type = Math.random().toString(36).substring(7).split('').join('.');
if (typeof reducer(undefined, { type }) === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined when probed with a random type. ` +
`Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined.`
);
}
});

var defaultState = finalReducers.map((r) => {
return r();
});
var stateShapeVerified;

return function combination(state = defaultState, action) {
var finalState = finalReducers.map((reducer, key) => {
var newState = reducer(state.get(key), action);
if (typeof newState === 'undefined') {
throw new Error(getErrorMessage(key, action));
}
return newState;
});

if ((
// Node-like CommonJS environments (Browserify, Webpack)
typeof process !== 'undefined' &&
typeof process.env !== 'undefined' &&
process.env.NODE_ENV !== 'production'
) ||
// React Native
typeof __DEV__ !== 'undefined' &&
__DEV__ // eslint-disable-line no-undef
) {
if (!stateShapeVerified) {
verifyStateShape(state, finalState);
stateShapeVerified = true;
}
}

return finalState;
};
}

0 comments on commit a654e7d

Please sign in to comment.