Skip to content

Commit

Permalink
Pass parent props into mapStateToProps
Browse files Browse the repository at this point in the history
Addresses part of reduxjs#52 by passing the parent props into mapStateWithProps.
  • Loading branch information
ide committed Aug 17, 2015
1 parent d13d76a commit 623d511
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 9 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ React Redux
Official React bindings for [Redux](https://github.com/gaearon/redux).
Performant and flexible.

[![npm version](https://img.shields.io/npm/v/react-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-redux)
[![npm version](https://img.shields.io/npm/v/react-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-redux)
[![npm downloads](https://img.shields.io/npm/dm/react-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-redux)
[![redux channel on slack](https://img.shields.io/badge/slack-redux@reactiflux-61DAFB.svg?style=flat-square)](http://www.reactiflux.com)

Expand Down Expand Up @@ -226,7 +226,7 @@ Connects a React component to a Redux store.

#### Arguments

* [`mapStateToProps(state): stateProps`] \(*Function*): If specified, the component will subscribe to Redux store updates. Any time it updates, `mapStateToProps` will be called. Its result must be a plain object, and it will be merged into the component’s props. If you omit it, the component will not be subscribed to the Redux store.
* [`mapStateToProps(state, props): stateProps`] \(*Function*): If specified, the component will subscribe to Redux store updates. Any time the store updates or the component receives new props, `mapStateToProps` will be called. Its result must be a plain object, and it will be merged into the component’s props. If you omit it, the component will not be subscribed to the Redux store.

* [`mapDispatchToProps(dispatch): dispatchProps`] \(*Object* or *Function*): If an object is passed, each function inside it will be assumed to be a Redux action creator. An object with the same function names, but bound to a Redux store, will be merged into the component’s props. If a function is passed, it will be given `dispatch`. It’s up to you to return an object that somehow uses `dispatch` to bind action creators in your own way. (Tip: you may use [`bindActionCreators()`](http://gaearon.github.io/redux/docs/api/bindActionCreators.html) helper from Redux.) If you omit it, the default implementation just injects `dispatch` into your component’s props.

Expand All @@ -240,7 +240,7 @@ A React component class that injects state and action creators into your compone

* It needs to be invoked two times. First time with its arguments described above, and second time, with the component: `connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)`.

* The `mapStateToProps` function takes a single argument of the entire Redux store’s state and returns an object to be passed as props. It is often called a **selector**. Use [reselect](https://github.com/faassen/reselect) to efficiently compose selectors and [compute derived data](http://gaearon.github.io/redux/docs/recipes/ComputingDerivedData.html).
* The `mapStateToProps` function takes the entire Redux store’s state and the "smart" component's props and returns an object to be passed as props. It is often called a **selector**. Use [reselect](https://github.com/faassen/reselect) to efficiently compose selectors and [compute derived data](http://gaearon.github.io/redux/docs/recipes/ComputingDerivedData.html).

* **To use `connect()`, the root component of your app must be wrapped into `<Provider>{() => ... }</Provider>` before being rendered.** You may also pass `store` as a prop to the `connect()`ed component, but it's not recommended because it's just too much trouble. Only do this for in non-fully-React codebases or to stub store in a unit test.

Expand Down Expand Up @@ -398,7 +398,7 @@ Make sure to check out [Troubleshooting Redux](http://gaearon.github.io/redux/do
### My views aren’t updating!

See the link above.
In short,
In short,

* Reducers should never mutate state, they must return new objects, or React Redux won’t see the updates.
* Make sure you either bind action creators with `mapDispatchToState` argument to `connect()` or with `bindActionCreators()` method, or that you manually call `dispatch()`. Just calling your `MyActionCreators.addTodo()` function won’t work because it just *returns* an action, but not *dispatches* it.
Expand Down
14 changes: 9 additions & 5 deletions src/components/createConnect.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default function createConnect(React) {
return function connect(mapStateToProps, mapDispatchToProps, mergeProps) {
const shouldSubscribe = Boolean(mapStateToProps);
const finalMapStateToProps = mapStateToProps || defaultMapStateToProps;
const statePropsDependOnParentProps = finalMapStateToProps.length > 1;
const finalMapDispatchToProps = isPlainObject(mapDispatchToProps) ?
wrapActionCreators(mapDispatchToProps) :
mapDispatchToProps || defaultMapDispatchToProps;
Expand All @@ -34,9 +35,9 @@ export default function createConnect(React) {
// Helps track hot reloading.
const version = nextVersion++;

function computeStateProps(store) {
function computeStateProps(store, props) {
const state = store.getState();
const stateProps = finalMapStateToProps(state);
const stateProps = finalMapStateToProps(state, props);
invariant(
isPlainObject(stateProps),
'`mapStateToProps` must return an object. Instead received %s.',
Expand Down Expand Up @@ -95,15 +96,15 @@ export default function createConnect(React) {
`or explicitly pass "store" as a prop to "${this.constructor.displayName}".`
);

this.stateProps = computeStateProps(this.store);
this.stateProps = computeStateProps(this.store, props);
this.dispatchProps = computeDispatchProps(this.store);
this.state = {
props: this.computeNextState()
};
}

recomputeStateProps() {
const nextStateProps = computeStateProps(this.store);
recomputeStateProps(props = this.props) {
const nextStateProps = computeStateProps(this.store, props);
if (shallowEqual(nextStateProps, this.stateProps)) {
return false;
}
Expand Down Expand Up @@ -163,6 +164,9 @@ export default function createConnect(React) {

componentWillReceiveProps(nextProps) {
if (!shallowEqual(nextProps, this.props)) {
if (statePropsDependOnParentProps) {
this.stateProps = computeStateProps(this.store, nextProps);
}
this.recomputeState(nextProps);
}
}
Expand Down
39 changes: 39 additions & 0 deletions test/components/connect.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,45 @@ describe('React', () => {
expect(div.props.stateThing).toBe('HELLO azbzcZ');
});

it('should pass state and parent props to mapStateToProps', () => {
const store = createStore(stringBuilder);

@connect(
(state, props) => ({idString: `[${props.id}] ${state}`}),
)
class Container extends Component {
render() {
return <div {...this.props}/>;
};
}

class OuterContainer extends Component {
constructor() {
super();
this.state = {id: 0};
}

render() {
return (
<Provider store={store}>
{() => <Container id={this.state.id} />}
</Provider>
);
}
}

const tree = TestUtils.renderIntoDocument(<OuterContainer />);
const div = TestUtils.findRenderedDOMComponentWithTag(tree, 'div');

expect(div.props.idString).toBe('[0] ');
store.dispatch({type: 'APPEND', body: 'a'});
expect(div.props.idString).toBe('[0] a');
tree.setState({id: 1});
expect(div.props.idString).toBe('[1] a');
store.dispatch({type: 'APPEND', body: 'b'});
expect(div.props.idString).toBe('[1] ab');
});

it('should merge actionProps into WrappedComponent', () => {
const store = createStore(() => ({
foo: 'bar'
Expand Down

0 comments on commit 623d511

Please sign in to comment.