0

Assume this is my state:

state={
  user:{
    name: 'Joe',
    condition:{
      isPrivate: true,
      premium: false
    }
  }
}

And this is the methods I can use to update user:

updateUser = (property, value)=>{
  // firstway. probably not a good one
  let user = this.state.user; 
  user[property] = value;
  this.setState({user})

  // second way. probably the best way
  let user = JSON.parse(JSON.stringify(this.state.user))
  user[property] = value;
  this.setState({user})
}

Although I know modifying the state directly is not a good practice but I'm getting the same result from both of them with no side effects so far. So why should I take this extra step to copy the state then modify it on the copied object while this slows down the operation (however so little)! So which one would be faster? what would be the side effects of the first method in the context of react? and finally what are the pros and cons of each method?

Reza Shoja
  • 887
  • 2
  • 11
  • 24
  • There is a concept called immutability. Worth reading about it. – Zorig Jun 26 '19 at 09:52
  • @Zorig I know what it is but in the context of react, I wanted to know what side effects it can have – Reza Shoja Jun 26 '19 at 09:53
  • Imagine simple scenario: you have component in which you define callback which triggers in 2 secs and console.log name of user. in 1 second u mutate the name of the user inside state. what will be printed? Its a lot easier to think about data in immutable way. – Pavel Kratochvil Jun 26 '19 at 10:01
  • A better way to do it: `this.setState(({user}) => ({ user: { ...user, [property]: value } }))` – Fraction Jun 26 '19 at 10:46

2 Answers2

1

The basic idea is avoid mutating objects, create new objects instead.

This mantra means that you should avoid direct mutations to javascript objects that you have in memory but, instead, you should create a new object each time.

You can use the ES6 spread operator in order to get a clone of your object. With the spread operator you can also update the properties of the clone, so that you perform the required update to the object properties.

This is the code you need:

updateUser = (property, value) => {
  const user = {...this.state.user, [property]: value}; // gets a clone of the current user and update its properties
  this.setState({ user });
}

The three dots in the syntax above are not a typo, they are the aforementioned ES6 spread operator

Based on my knowledge (I'm quite new to react) there are basically three reasons to avoid direct state mutation:

  • recalculating a new state each time is simpler than trying to update an existing state. When I say simpler I mean simpler from a conceptual and coding perspective. Creating a new object each time avoiding any kind of side effect will simplify your code and will reduce your bugs.

  • you can't be sure on how your component and its children components are using a given piece of state. That piece of state is used by your component and could be passed to its children components via props. If you only reason on your component in isolation you can't know how the children components are using that piece of state. What's gonna happen when you mutate the object in memory by changing its properties ? The response is who knows. You can have a series of side effects and, more important, you cannot be sure about what kind of side effects you will get if you reason only on your component in isolation. It really depends on how the component hierarchy is composed. Reasoning about side effects is always a mess, it's too risky to cope with them and a better approach is trying to avoid them.

  • react and react dom have been designed to update the browser DOM efficiently when the state is mutated by following the best practices of functional approach (no side effects and no direct state mutation). This means that, if you use react the way you are suggested to, react itself will have a better time in calculating the modifications to be applied to the DOM in order to redraw your components and then your app will perform better.

Enrico Massone
  • 6,464
  • 1
  • 28
  • 56
  • I know what three dots do, and this is not my answer, you're just copying the object in another way with shallow copying, I was deep copying so the `condition` property gets copied too. I just want to know the side effects of the first method in react – Reza Shoja Jun 26 '19 at 10:04
  • @BlueTurtle Even using the spread operator (like mentioned by Enrico) , `condition` property gets copied right? – Sriraman Jun 26 '19 at 10:21
  • @Sriraman I don't think so, spread operator just copies the object itself not nested objects, anyway, this is not my question! I don't want to know the proper way of copying the state, I wanted to know the possible effects of the first method and compare two methods' performance – Reza Shoja Jun 26 '19 at 10:29
  • @BlueTurtle the spread operator is not able to perform a deep copy – Enrico Massone Jun 26 '19 at 11:07
0

In response to your first method of updating state, you are getting a reference to the object nested in your state.

let user = this.state.user; 
user[property] = value;

In this chunk you have already updated the state, so you are actually performing a side effect. The call to setState() just reflects those changes in the UI(i.e. re-rendering of the component).

The reason for not modifying the state directly might be some unintentional updates in the state. For example, if you want to make an api call by modifying some of the data in this.state and sending it as the body of the request(Note that you don't want these updates to reflect in the UI), then modifying the state directly like you did in method 1 could cause some unwanted changes in the state and subsequent calls to setState() might expose some unwanted changes to the user of the application.

However in your example it's fine to use any of those methods but it might not be a good practice.

Hope this helps!