Skip to content

Commit 0fbe4fe

Browse files
committed
Supporting factory selectors in the object mapToProps argument
1 parent 76dd7fa commit 0fbe4fe

File tree

5 files changed

+280
-55
lines changed

5 files changed

+280
-55
lines changed

docs/api.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,12 @@ It does not modify the component class passed to it; instead, it *returns* a new
5252
<a id="connect-arguments"></a>
5353
#### Arguments
5454

55-
* [`mapStateToProps(state, [ownProps]): stateProps`] \(*Function*): If this argument is specified, the new component will subscribe to Redux store updates. This means that any time the store is updated, `mapStateToProps` will be called. The results of `mapStateToProps` must be a plain object, which will be merged into the component’s props. If you don't want to subscribe to store updates, pass `null` or `undefined` in place of `mapStateToProps`.
55+
* [`mapStateToProps(state, [ownProps]): stateProps`] \(*Function* or *Object*): If this argument is specified, the new component will subscribe to Redux store updates. This means that any time the store is updated, `mapStateToProps` will be called. The results of `mapStateToProps` must be a plain object, which will be merged into the component’s props. If you don't want to subscribe to store updates, pass `null` or `undefined` in place of `mapStateToProps`.
5656

5757
If your `mapStateToProps` function is declared as taking two parameters, it will be called with the store state as the first parameter and the props passed to the connected component as the second parameter, and will also be re-invoked whenever the connected component receives new props as determined by shallow equality comparisons. (The second parameter is normally referred to as `ownProps` by convention.)
5858

59+
If an object is passed, each function inside it is assumed to be a Redux selector (or -for advanced scenarios- a Factory function, see below). An object with the same keys, but with the result of invoking each one of its values like the normal `mapStateToProps` function, will be merged into the component’s props.
60+
5961
>Note: in advanced scenarios where you need more control over the rendering performance, `mapStateToProps()` can also return a function. In this case, *that* function will be used as `mapStateToProps()` for a particular component instance. This allows you to do per-instance memoization. You can refer to [#279](https://github.com/reactjs/react-redux/pull/279) and the tests it adds for more details. Most apps never need this.
6062
6163
>The `mapStateToProps` function's first argument is the entire Redux store’s state and it returns an object to be passed as props. It is often called a **selector**. Use [reselect](https://github.com/reactjs/reselect) to efficiently compose selectors and [compute derived data](http://redux.js.org/docs/recipes/ComputingDerivedData.html).
@@ -222,8 +224,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
222224
```js
223225
import { addTodo, deleteTodo } from './actionCreators'
224226

225-
function mapStateToProps(state) {
226-
return { todos: state.todos }
227+
const mapStateToProps = {
228+
todos: state => state.todos
227229
}
228230

229231
const mapDispatchToProps = {
@@ -357,6 +359,35 @@ function mapDispatchToPropsFactory(initialState, initialProps) {
357359
export default connect(mapStateToPropsFactory, mapDispatchToPropsFactory)(TodoApp)
358360
```
359361

362+
The object shorthand of `stateToProps` also accepts Factory functions
363+
364+
```js
365+
import { addTodo } from './actionCreators'
366+
367+
const always = x => () => x;
368+
369+
const mapStateToProps = {
370+
anotherProperty: (initialState, initialProps) =>
371+
always(200 + initialState[initialProps.another]),
372+
someProperty: (initialState, initilProps) => createSelector(...),
373+
todos: state => state.todos,
374+
}
375+
376+
function mapDispatchToPropsFactory(initialState, initialProps) {
377+
function goToSomeLink(){
378+
initialProps.history.push('some/link');
379+
}
380+
return function(dispatch){
381+
return {
382+
addTodo
383+
}
384+
}
385+
}
386+
387+
388+
export default connect(mapStateToProps, mapDispatchToPropsFactory)(TodoApp)
389+
```
390+
360391
<a id="connectAdvanced"></a>
361392
### `connectAdvanced(selectorFactory, [connectOptions])`
362393

package-lock.json

Lines changed: 17 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/connect/mapStateToProps.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
1-
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'
1+
import {
2+
wrapMapToPropsConstant,
3+
wrapMapToPropsFunc,
4+
wrapMapToPropsObject
5+
} from './wrapMapToProps'
26

37
export function whenMapStateToPropsIsFunction(mapStateToProps) {
48
return (typeof mapStateToProps === 'function')
59
? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
610
: undefined
711
}
812

13+
function isValidmapStateToPropsObj(mapStateToProps) {
14+
return typeof mapStateToProps === 'object' && Object
15+
.keys(mapStateToProps)
16+
.map(function (key) { return mapStateToProps[key] })
17+
.every(function (val) { return typeof val === 'function' })
18+
}
19+
20+
export function whenMapStateToPropsIsObject(mapStateToProps) {
21+
return (isValidmapStateToPropsObj(mapStateToProps)) ?
22+
wrapMapToPropsObject(mapStateToProps) :
23+
undefined
24+
}
25+
926
export function whenMapStateToPropsIsMissing(mapStateToProps) {
1027
return (!mapStateToProps)
1128
? wrapMapToPropsConstant(() => ({}))
@@ -14,5 +31,6 @@ export function whenMapStateToPropsIsMissing(mapStateToProps) {
1431

1532
export default [
1633
whenMapStateToPropsIsFunction,
34+
whenMapStateToPropsIsObject,
1735
whenMapStateToPropsIsMissing
1836
]

src/connect/wrapMapToProps.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function getDependsOnOwnProps(mapToProps) {
3535
// * On first call, verifies the first result is a plain object, in order to warn
3636
// the developer that their mapToProps function is not returning a valid result.
3737
//
38-
export function wrapMapToPropsFunc(mapToProps, methodName) {
38+
export function wrapMapToPropsFunc(mapToProps, methodName, isObjMapToProps = false) {
3939
return function initProxySelector(dispatch, { displayName }) {
4040
const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
4141
return proxy.dependsOnOwnProps
@@ -57,7 +57,7 @@ export function wrapMapToPropsFunc(mapToProps, methodName) {
5757
props = proxy(stateOrDispatch, ownProps)
5858
}
5959

60-
if (process.env.NODE_ENV !== 'production')
60+
if (process.env.NODE_ENV !== 'production' && !isObjMapToProps)
6161
verifyPlainObject(props, displayName, methodName)
6262

6363
return props
@@ -66,3 +66,39 @@ export function wrapMapToPropsFunc(mapToProps, methodName) {
6666
return proxy
6767
}
6868
}
69+
70+
function mapObject (obj, mapFn) {
71+
const result = {}
72+
Object
73+
.keys(obj)
74+
.forEach(function(key) { result[key] = mapFn(obj[key], key, obj) })
75+
return result
76+
}
77+
78+
export function wrapMapToPropsObject (mapStateToProps) {
79+
const wrappedMapToProps = mapObject(mapStateToProps, function (fn) {
80+
return wrapMapToPropsFunc(fn, 'mapStateToProps', true)
81+
})
82+
83+
const dependsOnOwnProps = Object
84+
.keys(wrappedMapToProps)
85+
.map(key => wrappedMapToProps[key])
86+
.some(getDependsOnOwnProps)
87+
88+
function initObjectSelector(...initArgs) {
89+
const initializedWraps = mapObject(wrappedMapToProps, function (fn) {
90+
return fn(...initArgs)
91+
})
92+
93+
function objectSelector (...selectorArgs) {
94+
return mapObject(initializedWraps, function (fn) {
95+
return fn(...selectorArgs)
96+
})
97+
}
98+
99+
objectSelector.dependsOnOwnProps = dependsOnOwnProps
100+
return objectSelector
101+
}
102+
103+
return initObjectSelector
104+
}

0 commit comments

Comments
 (0)