React: Why setState executes in setTimeout will become sync?

Created on 1 Mar 2018  Â·  5Comments  Â·  Source: facebook/react

componentDidMount(){
    setTimeout(() => {

            this.setState({ count: 1 }, () => {
                console.log(`banana`)
            })
            console.log(`lemen`)

            setTimeout(() => {
                console.log(`grape`)
            }, 0)

            this.setState({ count: 2 }, () => {
                console.log(`strawberry`)
            })

            console.log(`pear`)
        }, 0)
}

Why did lemen print behind banana?

Most helpful comment

@gaearon had an explanation for this on StackOverflow recently, but I can't find the comment. If I remember correctly (I think I do), setState is only async batched when it is called inside a React event handler, otherwise it is sync. Calling setState in setTimeout is therefore sync. I can confirm this behavior from my experience. This is not a bug, but it will probably change in the future (this was also mentioned in the issue I base my answer on).

I am not part of the React team, correct me if I wrote something stupid.

All 5 comments

@gaearon had an explanation for this on StackOverflow recently, but I can't find the comment. If I remember correctly (I think I do), setState is only async batched when it is called inside a React event handler, otherwise it is sync. Calling setState in setTimeout is therefore sync. I can confirm this behavior from my experience. This is not a bug, but it will probably change in the future (this was also mentioned in the issue I base my answer on).

I am not part of the React team, correct me if I wrote something stupid.

@gaearon had an explanation for this on StackOverflow recently, but I can't find the comment. If I remember correctly (I think I do), setState is only async batched when it is called inside a React event handler, otherwise it is sync. Calling setState in setTimeout is therefore sync. I can confirm this behavior from my experience. This is not a bug, but it will probably change in the future (this was also mentioned in the issue I base my answer on).

I am not part of the React team, correct me if I wrote something stupid.

If I call setState inside a event handler added with addEventHandler, it's also sync.

How can setState know it was called inside a React event handler?

@aztack you pass the event handlers to React and React is calling them when needed. From then on there are multiple ways to know if setState was called during the handler, the simplest one is this:

let isHandlerRunning = false

// handler is something you passed to onClick for example
function runHandler (handler) {
  isHandlerRunning = true
  handler()
  isHandlerRunning = false
}

// this is a setState implementation shell
function setState () {
  if (isHandlerRunning) {
    // do batching here
  } else {
    // do immediate update here
  }
}

This is called sync batching and implementing this requires the framework to have full control over the callback executions (it has to be the one calling the callbacks - like event handlers). React has control over the jsx event handlers but has no control over native event handlers or window timers. Sync batching those would require monkey patching (if I know correctly Angular zones do exactly this).

I am not into the React source code so the above snippets are just possible implementations. I can not guarantee that the React implementation looks like them.

Thank you @solkimicreb . I think you are right. I'll dig into React source code and see if I can find something like that.

Was this page helpful?
0 / 5 - 0 ratings