Skip to content

Pass parent props into mapStateToProps #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/[email protected]?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