Skip to content

Commit

Permalink
feat: allow to use Record and other Iterables as default state value (#…
Browse files Browse the repository at this point in the history
…58)

* fix: correct test glob pattern

* feat: allow using custom default state

* test: add tests for custom default state

* docs: update README with custom default state usage
  • Loading branch information
Velenir authored and gajus committed Feb 14, 2017
1 parent 833d7ce commit 50e3656
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 4 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,29 @@ const rootReducer = combineReducers({});
const store = createStore(rootReducer, initialState);
```

By default, if `state` is `undefined`, `rootReducer(state, action)` is called with `state = Immutable.Map()`. A different default function can be provided as the second parameter to `combineReducers(reducers, getDefaultState)`, for example:

```js
const StateRecord = Immutable.Record({
foo: 'bar'
});
const rootReducer = combineReducers({foo: fooReducer}, StateRecord);
// rootReducer now has signature of rootReducer(state = StateRecord(), action)
// state now must always have 'foo' property with 'bar' as its default value
```

When using `Immutable.Record` it is possible to delegate default values to child reducers:

```js
const StateRecord = Immutable.Record({
foo: undefined
});
const rootReducer = combineReducers({foo: fooReducer}, StateRecord);
// state now must always have 'foo' property with its default value returned from fooReducer(undefined, action)
```

In general, `getDefaultState` function must return an instance of `Immutable.Iterable` that implements `get`, `set` and `withMutations` methods. Such iterables are `List`, `Map`, `OrderedMap` and `Record`.

### Using with `react-router-redux`

`react-router-redux` [`routeReducer`](https://github.com/reactjs/react-router-redux/tree/v4.0.2#routerreducer) does not work with Immutable.js. You need to use a custom reducer:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
},
"scripts": {
"lint": "eslint ./src ./tests",
"test": "mocha --compilers js:babel-register ./tests/**/*.js",
"test": "mocha --compilers js:babel-register './tests/**/*.js'",
"build": "babel ./src --source-maps --out-dir ./dist",
"benchmark": "NODE_ENV=production node ./benchmarks/index.js",
"precommit": "npm run lint && npm run test"
Expand Down
4 changes: 2 additions & 2 deletions src/combineReducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import {
validateNextState
} from './utilities';

export default (reducers: Object): Function => {
export default (reducers: Object, getDefaultState: ?Function = Immutable.Map): Function => {
const reducerKeys = Object.keys(reducers);

// eslint-disable-next-line space-infix-ops
return (inputState: ?Immutable.Map = Immutable.Map(), action: Object): Immutable.Map => {
return (inputState: ?Function = getDefaultState(), action: Object): Immutable.Map => {
// eslint-disable-next-line no-process-env
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedInvocationParameterMessage(inputState, reducers, action);
Expand Down
92 changes: 91 additions & 1 deletion tests/combineReducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,98 @@ describe('combineReducers()', () => {
})
});

// eslint-disable-next-line no-undefined
// eslint-disable-next-line no-undefined
expect(rootReducer(undefined, {})).to.eql(initialState);
});
});
context('root reducer uses a custom Immutable.Iterable as default state', () => {
it('returns initial state as instance of supplied Immutable.Record', () => {
const defaultRecord = Immutable.Record({
bar: {
prop: 1
},
foo: undefined // eslint-disable-line no-undefined
});
const rootReducer = combineReducers({
bar: (state) => {
return state;
},
foo: (state = {count: 0}) => {
return state;
}
}, defaultRecord);

const initialState = {
bar: {
prop: 1
},
foo: {
count: 0
}
};

// eslint-disable-next-line no-undefined
const reducedState = rootReducer(undefined, {});

expect(reducedState.toJS()).to.deep.equal(initialState);
expect(reducedState).to.be.instanceof(defaultRecord);
});
it('returns initial state as instance of Immutable.OrderedMap', () => {
const rootReducer = combineReducers({
bar: (state = {prop: 1}) => {
return state;
},
foo: (state = {count: 0}) => {
return state;
}
}, Immutable.OrderedMap);

const initialState = {
bar: {
prop: 1
},
foo: {
count: 0
}
};

// eslint-disable-next-line no-undefined
const reducedState = rootReducer(undefined, {});

expect(reducedState.toJS()).to.deep.equal(initialState);
expect(reducedState).to.be.instanceof(Immutable.OrderedMap);
});
it('returns initial state as result of custom function call', () => {
const getDefaultState = () => {
return Immutable.Map({
bar: {
prop: 1
}
});
};
const rootReducer = combineReducers({
bar: (state) => {
return state;
},
foo: (state = {count: 0}) => {
return state;
}
}, getDefaultState);

const initialState = {
bar: {
prop: 1
},
foo: {
count: 0
}
};

// eslint-disable-next-line no-undefined
const reducedState = rootReducer(undefined, {});

expect(reducedState.toJS()).to.deep.equal(initialState);
expect(reducedState).to.be.instanceof(Immutable.Map);
});
});
});

0 comments on commit 50e3656

Please sign in to comment.