2

Parent component is a header

Child component is a form which is used to change values appearing in the header after a save which fires a redux action.

I set the child state with

constructor(props) {
    super(props);
    this.state = {
      object: { ...props.object },
      hidden: props.hidden,
    };
}

The form is used to render the state.object and modify the state.object. When I modify state.object, the props from the parent component change as well.

handleObjectChange = (event, key, subkey) => {
    console.log('props', this.props.object.params);
    console.log('state', this.state.object.params);
    event.preventDefault();
    const value = this.handlePeriod(event.target.value);
    this.setState((prevState) => {
      const object = { ...prevState.object };
      object[key][subkey] = value;
      return { object };
    });
}

Console output:

newvalueijusttyped
newvalueijusttyped

This behavior actually goes all the way up to modifying the redux store without ever having dispatched an action.

Would appreciate a solution for this issue

Update:

Changing the constructor to this solved the issue

constructor(props) {
    super(props);
    this.state = {
      object: JSON.parse(JSON.stringify(props.object)),
      hidden: props.hidden,
    };
 }

Why doesn't the object spread operator achieve what I'm trying to accomplish?

  • The spread operator clones only the first level. So objects inside objects are still passed by reference. Use [ImmutableJS](https://facebook.github.io/immutable-js/) for better performance – lumio Jul 26 '17 at 05:34

2 Answers2

2

Javascript object are assigned by reference so when you do

constructor(props) {
    super(props);
    this.state = {
      object: props.object,
      hidden: props.hidden,
    };
}

state is referencing the redux state object(if it is a redux state). So now when you use

this.setState((prevState) => {
  const object = { ...prevState.object };
  object[key][subkey] = value;
  return { object };
});

Although you would assume that you have cloned the object value into a new object. However Spread syntax does only a one level copy of the object.

From the Spread Syntax MDN docs:

Note: Spread syntax effectively goes one level deep while copying an array. Therefore, it may be unsuitable for copying multidimensional arrays as the following example shows (it's the same with Object.assign() and spread syntax).

var a = [1, [2], [3]]; var b = [...a]; b.shift().shift(); // 1 // Now array a is affected as well: [[], [2], [3]]

So effectively

object[key][subkey] = value;

changes the value directly in redux store.

Solution is create a nested copy like

  const object = { ...prevState.object,
                      [key]: {
                          ...prevState[key],
                          [subkey]: { ...prevState[key][subkey]}
                      }
                   };
  object[key][subkey] = value;
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
1

Objects in javascript are 'passed by reference'.

If you are passing the parent's props as state to the children, then when you change the state, you are in effect mutating the very same object that was passed to it.

Use Object.assign() to create a clone of the data before you make it part of the child's state.

kunal pareek
  • 1,285
  • 10
  • 21
  • 1
    Thanks for the suggestion. Using Object.assign() and the spread operator in the constructor did not solve the issue. Using JSON stringily then parse did solve the issue. Would appreciate any clarification on this behavior. – Aaron Stein Jul 26 '17 at 05:31
  • 1
    Object.assign won't make a deep clone. JSON.stringify does, but is slow. Better use ImmutableJS. – lumio Jul 26 '17 at 05:33
  • Aah this is interesting. The docs don't really talk about spread only doing a shallow clone anywhere. Will keep in mind. – kunal pareek Jul 26 '17 at 05:37
  • It's just mentioned in a passing sort of manner. Explains a problem I once had with spread as well. – kunal pareek Jul 26 '17 at 05:38
  • Yep, guess it's time for immutable. Thanks for the explanation. – Aaron Stein Jul 26 '17 at 05:43