Why is setState in reactjs Async instead of Sync?

后端 未结 7 1370
一生所求
一生所求 2020-11-21 22:58

I have just found that in react this.setState() function in any component is asynchronous or is called after the completion of the function that it was called i

相关标签:
7条回答
  • 2020-11-21 23:15

    Good article here https://github.com/vasanthk/react-bits/blob/master/patterns/27.passing-function-to-setState.md

    // assuming this.state.count === 0
    this.setState({count: this.state.count + 1});
    this.setState({count: this.state.count + 1});
    this.setState({count: this.state.count + 1});
    // this.state.count === 1, not 3
    
    Solution
    this.setState((prevState, props) => ({
      count: prevState.count + props.increment
    }));
    

    or pass callback this.setState ({.....},callback)

    https://medium.com/javascript-scene/setstate-gate-abc10a9b2d82 https://medium.freecodecamp.org/functional-setstate-is-the-future-of-react-374f30401b6b

    0 讨论(0)
  • 2020-11-21 23:18

    You can call a function after the state value has updated:

    this.setState({foo: 'bar'}, () => { 
        // Do something here. 
    });
    

    Also, if you have lots of states to update at once, group them all within the same setState:

    Instead of:

    this.setState({foo: "one"}, () => {
        this.setState({bar: "two"});
    });
    

    Just do this:

    this.setState({
        foo: "one",
        bar: "two"
    });
    
    0 讨论(0)
  • 2020-11-21 23:19

    Imagine incrementing a counter in some component:

      class SomeComponent extends Component{
    
        state = {
          updatedByDiv: '',
          updatedByBtn: '',
          counter: 0
        }
    
        divCountHandler = () => {
          this.setState({
            updatedByDiv: 'Div',
            counter: this.state.counter + 1
          });
          console.log('divCountHandler executed');
        }
    
        btnCountHandler = () => {
          this.setState({
            updatedByBtn: 'Button',
            counter: this.state.counter + 1
          });
          console.log('btnCountHandler executed');
        }
        ...
        ...
        render(){
          return (
            ...
            // a parent div
            <div onClick={this.divCountHandler}>
              // a child button
              <button onClick={this.btnCountHandler}>Increment Count</button>
            </div>
            ...
          )
        }
      }
    

    There is a count handler attached to both the parent and the child components. This is done purposely so we can execute the setState() twice within the same click event bubbling context, but from within 2 different handlers.

    As we would imagine, a single click event on the button would now trigger both these handlers since the event bubbles from target to the outermost container during the bubbling phase.

    Therefore the btnCountHandler() executes first, expected to increment the count to 1 and then the divCountHandler() executes, expected to increment the count to 2.

    However the count only increments to 1 as you can inspect in React Developer tools.

    This proves that react

    • queues all the setState calls

    • comes back to this queue after executing the last method in the context(the divCountHandler in this case)

    • merges all the object mutations happening within multiple setState calls in the same context(all method calls within a single event phase is same context for e.g.) into one single object mutation syntax (merging makes sense because this is why we can update the state properties independently in setState() in the first place)

    • and passes it into one single setState() to prevent re-rendering due to multiple setState() calls (this is a very primitive description of batching).

    Resultant code run by react:

    this.setState({
      updatedByDiv: 'Div',
      updatedByBtn: 'Button',
      counter: this.state.counter + 1
    })
    

    To stop this behaviour, instead of passing objects as arguments to the setState method, callbacks are passed.

        divCountHandler = () => {
              this.setState((prevState, props) => {
                return {
                  updatedByDiv: 'Div',
                  counter: prevState.counter + 1
                };
              });
              console.log('divCountHandler executed');
            }
    
        btnCountHandler = () => {
              this.setState((prevState, props) => {
                return {
                  updatedByBtn: 'Button',
                  counter: prevState.counter + 1
                };
              });
          console.log('btnCountHandler executed');
        }
    

    After the last method finishes execution and when react returns to process the setState queue, it simply calls the callback for each setState queued, passing in the previous component state.

    This way react ensures that the last callback in the queue gets to update the state that all of its previous counterparts have laid hands on.

    0 讨论(0)
  • 2020-11-21 23:26

    Yes, setState() is asynchronous.

    From the link: https://reactjs.org/docs/react-component.html#setstate

    • React does not guarantee that the state changes are applied immediately.
    • setState() does not always immediately update the component.
    • Think of setState() as a request rather than an immediate command to update the component.

    Because they think
    From the link: https://github.com/facebook/react/issues/11527#issuecomment-360199710

    ... we agree that setState() re-rendering synchronously would be inefficient in many cases

    Asynchronous setState() makes life very difficult for those getting started and even experienced unfortunately:
    - unexpected rendering issues: delayed rendering or no rendering (based on program logic)
    - passing parameters is a big deal
    among other issues.

    Below example helped:

    // call doMyTask1 - here we set state
    // then after state is updated...
    //     call to doMyTask2 to proceed further in program
    
    constructor(props) {
        // ..
    
        // This binding is necessary to make `this` work in the callback
        this.doMyTask1 = this.doMyTask1.bind(this);
        this.doMyTask2 = this.doMyTask2.bind(this);
    }
    
    function doMyTask1(myparam1) {
        // ..
    
        this.setState(
            {
                mystate1: 'myvalue1',
                mystate2: 'myvalue2'
                // ...
            },    
            () => {
                this.doMyTask2(myparam1); 
            }
        );
    }
    
    function doMyTask2(myparam2) {
        // ..
    }
    

    Hope that helps.

    0 讨论(0)
  • 2020-11-21 23:32

    I know this question is old, but it has been causing a lot of confusion for many reactjs users for a long time, including me. Recently Dan Abramov (from the react team) just wrote up a great explanation as to why the nature of setState is async:

    https://github.com/facebook/react/issues/11527#issuecomment-360199710

    setState is meant to be asynchronous, and there are a few really good reasons for that in the linked explanation by Dan Abramov. This doesn't mean it will always be asynchronous - it mainly means that you just can't depend on it being synchronous. ReactJS takes into consideration many variables in the scenario that you're changing the state in, to decide when the state should actually be updated and your component rerendered.
    A simple example to demonstrate this, is that if you call setState as a reaction to a user action, then the state will probably be updated immediately (although, again, you can't count on it), so the user won't feel any delay, but if you call setState in reaction to an ajax call response or some other event that isn't triggered by the user, then the state might be updated with a slight delay, since the user won't really feel this delay, and it will improve performance by waiting to batch multiple state updates together and rerender the DOM fewer times.

    0 讨论(0)
  • 2020-11-21 23:40

    You can use the following wrap to make sync call

    this.setState((state =>{
      return{
        something
      }
    })

    0 讨论(0)
提交回复
热议问题