Redux state is being edited without dispatching an

2019-08-03 05:48发布

问题:

I have an app with react + redux, and the problem is happening in the editing form.

Description of the issue

  1. click "edit" to edit a document
  2. Show the edit form
  3. I edit/delete one of the values
  4. instead of saving (dispatching an action to save) I return to any other page without saving
  5. then comeback to the description page for this object (where the "edit" button is located)

Result

The document is edited.

So, from the Portlets component, when I click on the edit button, I'll setState({isEditing: true}):

if(this.state.isEditing) {
  return (
    <div className="col-md-12">
      <div className="card">
        <div className="card-content">
          <PortletForm />
        </div>
      </div>
    </div>
  );

Then I open the <PortletForm /> component, and access again the data with connect():

function mapStateToProps(state, ownProps) {
  let portlet = {};

  const portletId = ownProps.match.params.id;

  if (state.portlets.length > 0) {
    portlet = Object.assign({}, state.portlets.find(portlet => portlet._id === portletId));
  }

  return { portlet: portlet };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(portletActions, dispatch)
  };
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PortletForm));

This is the constructor() in the editing form:

constructor(props) {
    super(props);

    this.state = {
      portlet: this.props.portlet,
      addNewKey: [],
      addNewKeyDisabled: false,
      newKey: '',
      newValue: ''
    };

    ...
}

This is the <input /> in jsx where that calls the editing function:

<input
  type="text"
  className="form-control"
  autoFocus={index === 0}
  name={Object.keys(key)}
  value={Object.values(key)}
  onChange={this._updatePortletKey} /> 

and _updatePortletKey(), where the problem seems to be happening:

_updatePortletKey(event) {
    const field = event.target.name;
    let editedDoc = Object.assign({}, this.state.portlet);
    editedDoc._keys.map((elem, index) => {
      const key = Object.keys(editedDoc._keys[index]);
      if(key[0] === field) {
        return editedDoc._keys[index][field] = event.target.value;
      }
    });
    debugger;
    this.setState({ portlet: editedDoc });
  }

So I create a new object, and assign it modified to the state, after some debuggin this is what I found:

  • When I'm accessing this function this.props.portlet contains the original object
  • Right after I return the new keys from the map function (where the debugger is placed) and before I edit the state, this.props.portlet contains the edited key!.

I really don't know what else to try, before this function I'm not triggering any dispatch or somethig, but I also tried a console.log() inside mapStateToProps() and after coming back to the non saved document the store is edited, I don't get it! I also checked that the parent function does not contain any other function that might be working at the same time... got nothing.

Any help is appreciated. Thanks in advance!

Edit: this is how this.props.portlet looks like:

{
    country: "AR"
    dev: true
    slug: "document-name"
    test: false
    version: 2
    _id: "5a798e53f2c5340bb08479f8"
    _keys: [
        { newKey: "new key value" }
    ]
}

回答1:

This is why suggested method to deal with redux is ImmutableJS or immutable-helper or the kind.

It is very easy to make mistakes otherwise.

Here is what happend:

// constructor
this.state = {
  portlet: this.props.portlet,  // you put props in the state
}

// change handler
let editedDoc = Object.assign({}, this.state.portlet);
// here a new object is created, but if the value is not primitive, it is by reference.
// this means, while editedDoc and this.state.portlet are different objects, "_key"'s value is shared between these object.

And this is a mistake I have seen many people making. Also, it is a pain to debug such cases. So please use a helper library.

If you insist on using es6 to construct new object, it could get ugly as the nesting increases.

// example 1
_updatePortletKey(event) {
  const field = event.target.name;

  this.setState({
    portlet: {
      ...this.state.portlet,
      _keys: this.state.portlet._keys.reduce((agg, el) => {
        const key = Object.keys(el); 
        // btw, what are you trying to do here !! (my logic might be slightly off, as I didn't fully understood the transformation, but you get the idea, right?)
        if(key[0] === field) { 
          return [...agg, {...el, [field]: event.target.value}]
        }
        return [...agg,el]
      }, [])
    }
  })