reduxjs / react-redux

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

componentWillReceiveProps not being called even though mapStateToProps returns different value #655

Closed RyanAtViceSoftware closed 7 years ago

RyanAtViceSoftware commented 7 years ago

I had initially posted this issue on the redux github here(https://github.com/reactjs/redux/issues/2317) but was advised to move it here.

Do you want to request a feature or report a bug?
bug

What is the current behavior? componentWillReceiveProps is not called even though mapStateToProps returns different values

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar. It'd be hard to create a minimal example here but I can show what's happening in my code. I'm having problems getting componentWillReceiveProps to be called reliably in my code. The logging output below is writing out the calls to mapStateToProps, componentWillReceiveProps, and render and as you can see the final call that I would expect to happen isn't happening even though mapStateToProps is returning state that would be different when using shallow compare.

Console Logging

detailsTabContainer.mapStateToProps {"drawerClosed":true}
detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
detailsTabContainer.js:3(2)  detailsTabContainer.componentWillReceiveProps (isClosing, isSubmitting) false true
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
detailsTabContainer.js:57 detailsTabContainer.render
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
detailsTabContainer.js:3(2)  detailsTabContainer.componentWillReceiveProps (isClosing, isSubmitting) false true
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
detailsTabContainer.js:57 detailsTabContainer.render
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(4) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(4) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":false}

code

import React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {submit} from 'redux-form';
import { CANVAS_OBJECT_TYPES } from '../../../../../constants/canvasObjectTypes';
import * as icActions from '../../../../../state/actions/internalControl.action';
import DetailsTab from './detailsTab';

class DetailsTabContainer extends React.Component {

  //This is a hack. Should be done with a callback from the flyout after the flyout is fully open.
  componentDidMount() {
    console.log('detailsTabContainer.componentDidMount');

    if (this.props.content.isNew) {
      setTimeout(() => {
        this.refs && this.refs.controlId && this.refs.controlId.focus();
      }, 200);
    }
  }

  componentWillReceiveProps(nextProps) {
    const isClosing = this.props.drawerClosed && nextProps.drawerClosed === false;
    const isSubmitting = !this.props.drawerSubmitted && nextProps.drawerSubmitted === true;

    console.log('detailsTabContainer.componentWillReceiveProps (isClosing, isSubmitting)', isClosing, isSubmitting);

    if (isClosing || isSubmitting) {
      this.props.actions.submit(CANVAS_OBJECT_TYPES.Control);
    }
  }

  render() {
    console.log('detailsTabContainer.render');
    // TODO: Ryan - remove spreading out of props and moved to more typed approach
    return <DetailsTab {...this.props}/>;
  }
}

const mapStateToProps = state => {
  const props = {
    drawerClosed: state.drawerState && state.drawerState.drawer && state.drawerState.drawer.show,
    drawerSubmitted:(state.drawerState && state.drawerState.drawer && state.drawerState.drawer.submitted) || false
  };

  console.log('detailsTabContainer.mapStateToProps', JSON.stringify(props));

  return props;
};

const mapDispatchToProps = dispatch => ({
  actions: bindActionCreators({submit}, dispatch)
})

export default connect(mapStateToProps, mapDispatchToProps)(DetailsTabContainer);

What is the expected behavior? That componentWillReceiveProps would be called after the last two calls to mapStateToProps as the result is different.

(2) detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":true,"drawerSubmitted":true}
detailsTabContainer.js:69 detailsTabContainer.mapStateToProps {"drawerClosed":false}

Which versions of Redux, and which browser and OS are affected by this issue? Did this work in previous versions of Redux?

    "react": "^15.3.2",
    "react-dom": "^15.3.2",
    "react-redux": "^5.0.3",
    "redux": "^3.6.0",

chrome Version 57.0.2987.110 (64-bit) mac seirra 10.12.3

Haven't tried it in previous versions of Redux

markerikson commented 7 years ago

Per discussion on Reactiflux, this appears to have been another case of accidental mutation. Closing.

flq commented 7 years ago

Hi there, what does "accidental mutation" mean? I am having a situation where componentWillReceiveProps is not been triggered even though the properties are changing.

RyanAtViceSoftware commented 7 years ago

@flq Take a look at this and see if it helps: http://redux.js.org/docs/Troubleshooting.html

markerikson commented 7 years ago

@flq : Also see http://redux.js.org/docs/faq/ReactRedux.html#react-not-rerendering , http://redux.js.org/docs/faq/ImmutableData.html , http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html , and http://redux.js.org/docs/recipes/reducers/PrerequisiteConcepts.html#immutable-data-management .

flq commented 7 years ago

Oh that, yeah. No, I create a new state. Ive been using this stack a few times, with not too much trouble lately. My problem is not a lack of rendering (the render method is called), but that the props-receive hook for updating internal state is not called. If time permits I'll try to produce a repro tomorrow.