Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Good" way how to update state (from its previous value) in React

From react doc

From the documentation (https://reactjs.org/docs/handling-events.html) I could see handling of on-off button changes by arrow function like:

handleClick() {
  this.setState(prevState => ({
    isToggleOn: !prevState.isToggleOn
  }));
}

Important thing in here (for me) is, that the new state value is derived from its previous state value. If I am right, the behaviour is the same as (without arrow function):

handleClick() {     
    this.setState(function (prevState){
      return {isToggleOn: !prevState.isToggleOn}
    });
}

or even without function at all:

handleClick() {
  this.setState({isToggleOn: this.state.isToggleOn ? 
    false : true});
}

1) Are there any differences in these except the code-style? All approaches seems to work for me and I could not find any difference (except readability).

2) I thought, this.setState is only "setter", how the "prevState" can be passed into that function?

3) Is it mistake to write the code like this (3th approach)? I became from "old-school" and I did not develop for more than 5 years, now I am returning to dev slowly. The first one example is hardly understandable for me (because of arrow functions and anonymous function), second one is clearer a little bit but the last one is the most self-explaining to me.


Real life example

"Real learn (life)" example what I am struggling right now: As I am learning, I am trying to write my own calculator (starting with only basic functionality). Because of bad "design" (I found out it will be much better to use some array, fields, lists instead of only "variableA, variableB", but never the less..). My app structure is:

  • Calculator
    • ResultHeader
      • Input (for result)
      • Input (for printing the equation)
    • CalcButton (0)
      • Button
    • ...
    • CalcButton (X)
      • Button

CalcButton contains props for its value ([0...9], x, /, +, -, C, ...) and event handlers (numberClick, operatorClick). When number button is pressed, it lifts its state to Calculator, which adds the number to valueA or valueB, depends on the state of "isFirst". operatorClick contains switch for its own functionality and if x, /, +, - was pressed, it sets isFirst on false and user can continue with second number (valueB). Now the code of this handler:

onNumberClick(e) {
    function appendA() {
        return {valueA: this.state.valueA + e};
    }

    function appendB() {
        return {valueB: this.state.valueB + e};
    }

    this.setState(this.state.isFirst ? appendA : appendB);

    /*
    if(this.state.isFirst) { 
        this.setState({valueA: this.state.valueA + e});
    } else {
        this.setState({valueB: this.state.valueB + e});
    }
    */
}

Commented code is basically the same as 3th example, even the uncommented variant, only with some "readability" improvements. Last 2 days I am stuck if there is any possibility to use some anonymous arrow function so I do not need to duplicate the code (one time for valueA, one time for valueB). Because of javascript and its lack of object referencing, the only solution I was able to figure was to pass the argument via string ("valueA" || "valueB") but I think it is not nice solution. The important thing in here is (as in the example at the top), the new state value is derived from its previous value (that is why I am still thinking about using arrow anonymous function).

Can somebody show me some examples how to achieve that? Is arrow function necessary in here (is it even possible to do this by arrow function?) or is this "code" just fine and I am finding problems where there are none?

like image 578
tomdelahaba Avatar asked Dec 05 '25 14:12

tomdelahaba


1 Answers

React batches state updates. If the state is updated with setState in one section of the code, and then some other part of the code synchronously calls setState again, the prior value set may not be reflected in this.state yet. For example:

class App extends React.Component {
  state = {};
  componentDidMount() {
    this.setState({ foo: 'foo' });
    console.log('after setting state, this.state.foo is:', this.state.foo);
    // so, further logic that depends on this.state.foo being 'foo' will fail
  }
  render() {
    return 'My App';
  }
}

ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

In other words, you can't reliably do

this.setState({ someProp: someVal });
// refer to this.state.someProp here
// because it may well refer to the state before it was updated
// not the new state which contains someVal

If, like in your particular situation, you do not have multiple synchronous state updates - then there is no problem, because the state will update almost immediately after a click, and before another click can be processed. So, your

handleClick() {
  this.setState({isToggleOn: this.state.isToggleOn ? 
    false : true});
}

is safe - or, more concisely:

handleClick() {
  this.setState({isToggleOn: !this.state.isToggleOn });
}

as long as you don't do something else immediately before or after handleClick that depends on or changes this.state.isToggleOn.

You only need the functional version, eg

handleClick() {     
    this.setState(function (prevState){
      return {isToggleOn: !prevState.isToggleOn}
    });
}

if the state may have just been set (synchronously) by code above, before a re-render. See the first snippet in this answer for an example - in such a situation, if you wanted to call this.setState again, and use the value for this.state.foo that was just set, you'd want to use the callback form.

I thought, this.setState is only "setter", how the "prevState" can be passed into that function?

React internals keep track of the current value of the state when setState is called, and they pass the updated value into the callback, despite the fact that the component's this.state may not have been updated yet.

Is it mistake to write the code like this (3th approach)?

Not at all, as long as you're aware of the limitation. (If your setState does not depend on state values that have just been set by a call to this.setState earlier, your third approach is just fine.)

Just for a contrived example, if your app had a button that incremented a counter, and another button that increases a state value by the amount of the counter, one option would be to use the callback version in combination with multiple calls to setState:

updateSum() {
  for (let i = 0; i < this.state.count; i++) {
    this.incrementByOne();
  }
}
incrementByOne() {
  this.setState(prev => ({ sum: prev.sum + 1 }));
}

The above incrementByOne, since it's being called multiple times, would only work if it uses the callback version. If it did

this.setState({ sum: this.state.sum + 1 });

it would not function properly, since this.state.sum would not be updated from the previous synchronous calls to incrementByOne.

like image 145
CertainPerformance Avatar answered Dec 07 '25 04:12

CertainPerformance