Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I directly modify a component's state, really?

I understand that React tutorials and documentation warn in no uncertain terms that state should not be directly mutated and that everything should go through setState.

I would like to understand why, exactly, I can't just directly change state and then (in the same function) call this.setState({}) just to trigger the render.

E.g.: The below code seems to work just fine:

const React = require('react');  const App = React.createClass({   getInitialState: function() {     return {       some: {         rather: {           deeply: {             embedded: {               stuff: 1,             },           },         },       },     },   };   updateCounter: function () {     this.state.some.rather.deeply.embedded.stuff++;     this.setState({}); // just to trigger the render ...   },   render: function() {     return (       <div>         Counter value: {this.state.some.rather.deeply.embedded.stuff}         <br></br>         <button onClick={this.updateCounter}>Increment</button>       </div>     );   }, });  export default App; 

I am all for following conventions but I would like to enhance my further understanding of how ReactJS actually works and what can go wrong or is it sub-optimal with the above code.

The notes under the this.setState documentation basically identify two gotchas:

  1. That if you mutate state directly and then subsequently call this.setState this may replace (overwrite?) the mutation you made. I don't see how this can happen in the above code.
  2. That setState may mutate this.state effectively in an asynchronous / deferred way and so when accessing this.state right after calling this.setState you are not guaranteed to access the final mutated state. I get that, by this is not an issue if this.setState is the last call of the update function.
like image 788
Marcus Junius Brutus Avatar asked Jun 10 '16 19:06

Marcus Junius Brutus


People also ask

Why can't we change state directly in React?

One should never update the state directly because of the following reasons: If you update it directly, calling the setState() afterward may just replace the update you made. When you directly update the state, it does not change this.

Can we change state directly in React?

To make the state change, React gives us a setState function that allows us to update the value of the state. Calling setState automatically re-renders the entire component and all its child components. We don't need to manually re-render as seen previously using the renderContent function.

Can a developer directly mutate React native state?

In React, the state is immutable. In simple terms it means that you should not modify it directly. Instead a new object should be created to set the state using setState .

Can you modify the values of state?

Changing the state ObjectsetState() method. When a value in the state object changes, the component will re-render, meaning that the output will change according to the new value(s).


1 Answers

This answer is to provide enough information to not change/mutate the state directly in React.

React follows Unidirectional Data Flow. Meaning, the data flow inside react should and will be expected to be in a circular path.

React's Data flow without flux

enter image description here

To make React work like this, developers made React similar to functional programming. The rule of thumb of functional programming is immutability. Let me explain it loud and clear.

How does the unidirectional flow works?

  • states are a data store which contains the data of a component.
  • The view of a component renders based on the state.
  • When the view needs to change something on the screen, that value should be supplied from the store.
  • To make this happen, React provides setState() function which takes in an object of new states and does a compare and merge(similar to object.assign()) over the previous state and adds the new state to the state data store.
  • Whenever the data in the state store changes, react will trigger an re-render with the new state which the view consumes and shows it on the screen.

This cycle will continue throughout the component's lifetime.

If you see the above steps, it clearly shows a lot of things are happening behind when you change the state. So, when you mutate the state directly and call setState() with an empty object. The previous state will be polluted with your mutation. Due to which, the shallow compare and merge of two states will be disturbed or won't happen, because you'll have only one state now. This will disrupt all the React's Lifecycle Methods.

As a result, your app will behave abnormal or even crash. Most of the times, it won't affect your app because all the apps which we use for testing this are pretty small.

And another downside of mutation of Objects and Arrays in JavaScript is, when you assign an object or an array, you're just making a reference of that object or that array. When you mutate them, all the reference to that object or that array will be affected. React handles this in a intelligent way in the background and simply give us an API to make it work.

Most common errors done when handling states in React

// original state this.state = {   a: [1,2,3,4,5] }  // changing the state in react // need to add '6' in the array  // bad approach const b = this.state.a.push(6) this.setState({   a: b })  

In the above example, this.state.a.push(6) will mutate the state directly. Assigning it to another variable and calling setState is same as what's shown below. As we mutated the state anyway, there's no point assigning it to another variable and calling setState with that variable.

// same as  this.state.a.push(6) this.setState({}) 

Many people do this. This is so wrong. This breaks the beauty of React and is bad programming practice.

So, what's the best way to handle states in React? Let me explain.

When you need to change 'something' in the existing state, first get a copy of that 'something' from the current state.

// original state this.state = {   a: [1,2,3,4,5] }  // changing the state in react // need to add '6' in the array  // create a copy of this.state.a // you can use ES6's destructuring or loadash's _.clone() const currentStateCopy = [...this.state.a] 

Now, mutating currentStateCopy won't mutate the original state. Do operations over currentStateCopy and set it as the new state using setState().

currentStateCopy.push(6) this.setState({   a: currentStateCopy }) 

This is beautiful, right?

By doing this, all the references of this.state.a won't get affected until we use setState. This gives you control over your code and this'll help you write elegant test and make you confident about the performance of the code in production.

To answer your question,

Why can't I directly modify a component's state?


Yes, you can. But, you need to face the following consequences.

  1. When you scale, you'll be writing unmanageable code.
  2. You'll lose control of state across components.
  3. Instead of using React, you'll be writing custom codes over React.

Immutability is not necessary thing because JavaScript is single threaded. But, It's a good to follow practice which will help you in the long run.

PS. I've written about 10000 lines of mutable React JS code. If it breaks now, I don't know where to look into because all the values are mutated somewhere. When I realized this, I started writing immutable code. Trust me! That's the best thing you can do it to a product or an app.

Hope this helps!

like image 152
Pranesh Ravi Avatar answered Oct 03 '22 21:10

Pranesh Ravi