yangguang1029
yangguang1029

Reputation: 1823

Why sometimes render is called immediately after setState and sometimes not

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?

Upvotes: 2

Views: 921

Answers (1)

Sagiv b.g
Sagiv b.g

Reputation: 31024

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>

Upvotes: 1

Related Questions