Skip to content

Commit 50e3656

Browse files
Velenirgajus
authored andcommitted
feat: allow to use Record and other Iterables as default state value (#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
1 parent 833d7ce commit 50e3656

File tree

4 files changed

+117
-4
lines changed

4 files changed

+117
-4
lines changed

README.md

+23
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,29 @@ const rootReducer = combineReducers({});
3737
const store = createStore(rootReducer, initialState);
3838
```
3939

40+
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:
41+
42+
```js
43+
const StateRecord = Immutable.Record({
44+
foo: 'bar'
45+
});
46+
const rootReducer = combineReducers({foo: fooReducer}, StateRecord);
47+
// rootReducer now has signature of rootReducer(state = StateRecord(), action)
48+
// state now must always have 'foo' property with 'bar' as its default value
49+
```
50+
51+
When using `Immutable.Record` it is possible to delegate default values to child reducers:
52+
53+
```js
54+
const StateRecord = Immutable.Record({
55+
foo: undefined
56+
});
57+
const rootReducer = combineReducers({foo: fooReducer}, StateRecord);
58+
// state now must always have 'foo' property with its default value returned from fooReducer(undefined, action)
59+
```
60+
61+
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`.
62+
4063
### Using with `react-router-redux`
4164

4265
`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:

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
},
4141
"scripts": {
4242
"lint": "eslint ./src ./tests",
43-
"test": "mocha --compilers js:babel-register ./tests/**/*.js",
43+
"test": "mocha --compilers js:babel-register './tests/**/*.js'",
4444
"build": "babel ./src --source-maps --out-dir ./dist",
4545
"benchmark": "NODE_ENV=production node ./benchmarks/index.js",
4646
"precommit": "npm run lint && npm run test"

src/combineReducers.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import {
44
validateNextState
55
} from './utilities';
66

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

1010
// eslint-disable-next-line space-infix-ops
11-
return (inputState: ?Immutable.Map = Immutable.Map(), action: Object): Immutable.Map => {
11+
return (inputState: ?Function = getDefaultState(), action: Object): Immutable.Map => {
1212
// eslint-disable-next-line no-process-env
1313
if (process.env.NODE_ENV !== 'production') {
1414
const warningMessage = getUnexpectedInvocationParameterMessage(inputState, reducers, action);

tests/combineReducers.js

+91-1
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,98 @@ describe('combineReducers()', () => {
6565
})
6666
});
6767

68-
// eslint-disable-next-line no-undefined
68+
// eslint-disable-next-line no-undefined
6969
expect(rootReducer(undefined, {})).to.eql(initialState);
7070
});
7171
});
72+
context('root reducer uses a custom Immutable.Iterable as default state', () => {
73+
it('returns initial state as instance of supplied Immutable.Record', () => {
74+
const defaultRecord = Immutable.Record({
75+
bar: {
76+
prop: 1
77+
},
78+
foo: undefined // eslint-disable-line no-undefined
79+
});
80+
const rootReducer = combineReducers({
81+
bar: (state) => {
82+
return state;
83+
},
84+
foo: (state = {count: 0}) => {
85+
return state;
86+
}
87+
}, defaultRecord);
88+
89+
const initialState = {
90+
bar: {
91+
prop: 1
92+
},
93+
foo: {
94+
count: 0
95+
}
96+
};
97+
98+
// eslint-disable-next-line no-undefined
99+
const reducedState = rootReducer(undefined, {});
100+
101+
expect(reducedState.toJS()).to.deep.equal(initialState);
102+
expect(reducedState).to.be.instanceof(defaultRecord);
103+
});
104+
it('returns initial state as instance of Immutable.OrderedMap', () => {
105+
const rootReducer = combineReducers({
106+
bar: (state = {prop: 1}) => {
107+
return state;
108+
},
109+
foo: (state = {count: 0}) => {
110+
return state;
111+
}
112+
}, Immutable.OrderedMap);
113+
114+
const initialState = {
115+
bar: {
116+
prop: 1
117+
},
118+
foo: {
119+
count: 0
120+
}
121+
};
122+
123+
// eslint-disable-next-line no-undefined
124+
const reducedState = rootReducer(undefined, {});
125+
126+
expect(reducedState.toJS()).to.deep.equal(initialState);
127+
expect(reducedState).to.be.instanceof(Immutable.OrderedMap);
128+
});
129+
it('returns initial state as result of custom function call', () => {
130+
const getDefaultState = () => {
131+
return Immutable.Map({
132+
bar: {
133+
prop: 1
134+
}
135+
});
136+
};
137+
const rootReducer = combineReducers({
138+
bar: (state) => {
139+
return state;
140+
},
141+
foo: (state = {count: 0}) => {
142+
return state;
143+
}
144+
}, getDefaultState);
145+
146+
const initialState = {
147+
bar: {
148+
prop: 1
149+
},
150+
foo: {
151+
count: 0
152+
}
153+
};
154+
155+
// eslint-disable-next-line no-undefined
156+
const reducedState = rootReducer(undefined, {});
157+
158+
expect(reducedState.toJS()).to.deep.equal(initialState);
159+
expect(reducedState).to.be.instanceof(Immutable.Map);
160+
});
161+
});
72162
});

0 commit comments

Comments
 (0)