In your case reading old value is impossible. Under "It may batch or defer the update until later" it means just a case
this.setState({a: 11});
console.log(this.state.a);
so setState may just add a change to queue but not directly update this.state. But it does not mean you can just change input with triggering handleChange and then click button triggering handleSubmit and .state is still not updated. It because how event loop works - if some code is executing browser will not process any event(you should experience cases when UI freezes for a while).
So the only thing to reproduce 'race condition' is to run one handler from another:
handleChange(event) {
this.setState({value: event.target.value});
this.handleSubmit();
}
This way, yes, you will get previous value shown in alert.
For such a cases .setState takes optional callback parameter
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
Applied to your code it'd look like
handleChange(event) {
this.setState({value: event.target.value}, this.handleSubmit);
}
PS And sure your code may delay setState with setTimeout on your own like
handleChange({target: {value}}) {
setTimeout(() => {
this.setState({value});
}, 5000);
}
And there is no way to ensure handleSubmit is operating on latest value. But in such a case it's all on your shoulders how to handle that.
[UPD] some details on how async works in JS. I have heard different terms: "event loop", "message queue", "microtask/task queue" and sometimes it means different things(and there is a difference actually). But to make things easier let's assume there is just single queue. And everything async like event handlers, Promise.then(), setImmediate() just go to the end of this queue.
This way each setState(if it's in batch mode) does 2 things: adds changeset to stack(it could be array variable) and set up additional task into queue(say with setImmediate). This additional task will process all stacked changes and run rerender just once.
Even if you would be so fast to click Submit button before those deferred updater is executed event handler would go to the end of queue. So event handler will definitely run after all batched state's changes are applied.
Sorry, I cannot just refer to React code to prove because updater code looks really complex for me. But I found article that has many details how it works under the hood. Maybe it gives some additional information to you.
[UPD] met nice article on microtasks, macrotasks and event loop: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f?gi=599c66cc504c
it does not change the result but makes me understand all that better