reduxjs / react-redux

Official React bindings for Redux
https://react-redux.js.org
MIT License
23.37k stars 3.37k forks source link

componentWillReceiveProps and mapDispatchToProps #221

Closed iammerrick closed 8 years ago

iammerrick commented 8 years ago

I am running into an issue where componentWillReceiveProps is being called to early, namely before React Redux has had the opportunity to bind a different function based on the props passed. The previous function is still bound when componentWillReceiveProps is called. Take for example this component:

const App = ReactRedux.connect((state, props) => ({
  name: props.userId ? state.user.name : state.group.groupName
}), (dispatch, props) => {
  return Redux.bindActionCreators({
    load: props.userId ? loadUser : loadGroup
  }, dispatch);
})(class extends React.Component {
  componentWillMount() {
    this.props.load();
  }
  componentWillReceiveProps() {
    this.props.load();
  }
  render() {
    return <div>Hello {this.props.name}!</div>;
  }
});

We want to bind a different function to load (based on the userId prop) and a different data to props based on the property provided. Unfortunately when componentWillReceiveProps is called, the old function is still bound to load. This can be "hacked" by introducing a setTimeout in your componentWillReceiveProps that makes sure the next component is actually bound.

I've made a full example of this on jsbin: http://jsbin.com/faqama/edit?html,js,output

iammerrick commented 8 years ago

I'm guessing this is the result of shallowEqual checking object keys and using the same object keys for different functions? https://github.com/rackt/react-redux/blob/master/src/utils/shallowEqual.js

gaearon commented 8 years ago

No, this is correct behavior.

When componentWillReceiveProps is executed, this.props still points to the old props. However it receives nextProps as argument.

  componentWillMount() {
    this.props.load();
  }
  componentWillReceiveProps(nextProps) {
    nextProps.load();
  }

It's also a good idea to only call the action when IDs change because componentWillReceiveProps will be called on any prop change.

  componentWillMount() {
    this.props.load();
  }
  componentWillReceiveProps(nextProps) {
    if (this.props.userId !== nextProps.userId) {
      nextProps.load();
    }
  }
iammerrick commented 8 years ago

Oh my goodness I feel silly, you are right! Thank you! :-D