nick
nick

Reputation: 3239

When using the spread syntax, should I copy the whole object, level by level?

When using Redux, I have my initial state:

const initialState = {
  foo: {
    bar: {
      foobar: 1,
      barfoo: 2
    }
  },
  xyz: true,
  abc: {
    jkl: [1,2,3,4]
  }
}

Then I have the reducer, and inside a switch. Suppose I have a case X in which I want to change xyz to false.

Is this enough?

return {
  ...state,
  xyz: false 
}

or should I do?

return {
  foo: {
    bar: {
      ...state.foo.bar
    }
  },
  xyz: false,
  abc: {
    jkl: [...state.abc.jkl]
  }
}

Upvotes: 1

Views: 119

Answers (3)

salezica
salezica

Reputation: 76929

Dan's answer gives you a practical and working syntax (do that! it's cleaner), but let's understand why.

Each spread copy is shallow, not deep. Only the first level is duplicated. For example, let's say you attempt to clone an object this way:

> let original = { someNumber: 1, someArray: [1, 2] }
> let copy = { ...original }

The objects original and copy are distinct. If you set properties, they won't reflect each other.

> x2.someNumber = 2
> x2.newProperty = "hello"

> console.log(x1)
{ someNumber: 1, someArray: [1,2] } // same someNumber, no newProperty!

But the value of each individual key is not duplicated, it's just a reference to the original. The someArray property references the very same array instance in both objects. So:

> console.log(x1.someArray)
[1, 2]

> x2.someArray.push(3)
> console.log(x1.someArray)
[1, 2, 3]

Both original.someArray and copy.someArray are referencing the same array instance. Two references in two objects, but only one underlying array.

There's no easy, 100% foolproof way to actually clone an object, because not all objects are simple JSON-like dictionaries. But you have some options in this other answer.

When working with React and Redux, many problems can be avoided by using a library like ImmutableJS, which ensures each object is distinct and every modification produces a different object. The performance is good and the syntax more comfortable in many cases, as well.

Upvotes: 0

Dan Zuzevich
Dan Zuzevich

Reputation: 3831

No need to do your second implementation! That would create a catastrophe of reducers lol.

Just use:

return {
  ...state,
  xyz: false 
}

Not only is this the way that is advised in Redux apps, imagine having to debug reducers where you use your second implementation, and you make typos.

On the case of nested properties, you would need to do something like this:

return {
   ...state, 
   foo: { 
      bar: { ...state.foo.bar, roflWaffle: 3 } 
   } 
}

Also, recommend checking out other answers here too, very helpful in depth information that will greatly help you understand how JavaScript and Redux works under the hood.

Upvotes: 1

customcommander
customcommander

Reputation: 18901

It is enough if you are aware that the spread operator only does a shallow clone:

const a = {x: {y: 10}};
const b = {...a};

b.x.y = 42;

console.log(b.x.y); //=> 42
console.log(a.x.y); //=> 42

I understand that with Redux you want to return new state. Just know that this potentially opens up the gate to unwanted mutations and side effects.

With this simple example, I have shown how a simple reducer can potentially tamper with the history of your Redux states.

Upvotes: 1

Related Questions