2

here is my codes:

class TestState extends Component{
    constructor(props){
        super(props);
        this.state={
            text:"123"
        }
        this._hello = this._hello.bind(this)
    }

    _hello(){
        console.log("guangy will set state...");
        let num = Math.floor(Math.random()*100);
        this.setState({text: ""+num });
        console.log("guangy after set state...");
    }

    componentWillUpdate(){
        console.log("guangy componentWillUpdate")
    }

    render(){
        console.log("guangy render.....")
        return(<View>
            <Text>{this.state.text}</Text>
            <Button title="click" onPress={
                ()=>{
                    this._hello();
                }
            }/>
            <Button title="click1" onPress={
                ()=>{
                    setTimeout(()=>{
                        this._hello();
                    }, 10);
                }
            }/>
        </View>);
    }
}

when i clicked the first button, the log is:

guangy will set state...
guangy after set state...
guangy componentWillUpdate
guangy render.....

and logs when clicked the second button:

guangy will set state...
guangy componentWillUpdate
guangy render.....
guangy after set state...

i think the render function should be called asynchronous, and actually in most situation it is, like when i clicked the first button, but when i clicked the second button, the render function seems to be called synchronous, because the "after set state" log is printed after the "render" log. why does this happen?

Panup Pong
  • 1,871
  • 2
  • 22
  • 44
yangguang1029
  • 1,813
  • 14
  • 16
  • The reason being that not render but setState is asynchronous, https://stackoverflow.com/questions/41278385/calling-setstate-doesnt-mutate-state-immediately/41278440#41278440 – Shubham Khatri Dec 02 '17 at 08:57
  • @ShubhamKhatri i know setState is asynchronous, that's why "render..." is printed after "after set state...", but in the second situation, why the render function is called immediately after setState, it looks like synchronous – yangguang1029 Dec 02 '17 at 09:06
  • async doesn't mean that the statement following will be executed at a later point, but it means that anything can happen – Shubham Khatri Dec 02 '17 at 09:20
  • i guess it's something about "event loop", setTimeout callback and render are both called at the end of a frame event loop, but i dont know much about how reactNative works internally – yangguang1029 Dec 02 '17 at 09:35
  • There is no difference between these two buttons onClick processing, so you have pretty random results and discussion why they are different doesn't make sense. – elmeister Dec 02 '17 at 09:40
  • The difference between both buttons is the `setTimeout` on the second button will "push" the execution to the end of the Que in the event loop. i strongly recommend watching [this video](https://www.youtube.com/watch?v=8aGhZQkoFbQ) about event loops. – Sagiv b.g Dec 02 '17 at 09:47
  • @Sag1v Could you explain how? Because I guess event loop has little to do with this. – Prakash Sharma Dec 02 '17 at 10:02
  • @Prakashsharma i've added an answer with an example – Sagiv b.g Dec 02 '17 at 15:29
  • @ShubhamKhatri i think the question is why setState is __NOT__ asynchronous at some situations. – Sagiv b.g Dec 02 '17 at 16:25

1 Answers1

1

As per the DOCS

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately. setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.

So one would think that setState is asynchronous. But if we look at some key words like:

React MAY delay it

setState() does not ALWAYS immediately update the component.

It MAY batch or defer the update until later.

So are we to think that setState is may or may not be an asynchronous operation?
Well, yep.

This is setState taken from the source code:

ReactComponent.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
    typeof partialState === 'function' ||
    partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
    'function which returns an object of state variables.'
  );
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

Looks like a normal synchronous function, well actually setState as it self isn't asynchronous but the effect of its execution may be.

I've done some testing myself and i realized that react will only update the state Asynchronously is when react has control of the entire flow, where react can't control of the flow, which means the the execution context is outside of it, then react will update the state immediately.

What can take react's control then?
Things like setTimeout, setInterval ajax request and other webApi's.
Even an event handler that attached outside react will trigger such behavior.

In fact here is a little snippet to satisfy our experiment.
I have a React component App which has a state with a myValue key and a method named onClick.
This method logs the current state, then call setstate then logs the state again.

App renders 3 elements:

  • The current value of myValue.
  • A button that we attach onClick through the react API.
  • A button that we attach onClick through the addEventListener API (out side of react mind you).

When we click on the first button when react has control over the flow, the state is updated asynchronously.

When we click the second button, react will update the state immediately.

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      myVal: 0
    }
  }

  componentDidMount() {
    const el = document.getElementById('btn');
    el.addEventListener('click', this.onClick);
  }

  onClick = () => {
    console.log('Pre setState', this.state.myVal);
    this.setState({ myVal: this.state.myVal + 1 });
    console.log('Post setState', this.state.myVal);
    console.log('-------------------');
  }


  render() {
    const { myVal } = this.state;
    return (
      <div>
        <div>{myVal}</div>
        <button onClick={this.onClick}>Managed by react</button>
        <button id='btn'>Not Managed by react</button>
      </div>);
  }
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99