The subject of closures and React useState hook

very young Padawan in the React universe here,

This question revolves around the inner mechanisms of the useState() hook. My gratitude in advance for your time taken! and my sincerest apologies for the lengthy post.

Would this explanation perfectly describe what is going on ?

Looking at the 1st challenge (Challenge 1 of 2: Fix a request counter)in the page: Queueing a series of state updates within the React.dev DOCs,

this line of code exists within the function component:

async function handleClick() {
    setPending(p => p + 1);
    await delay(3000);
    setPending(p => p - 1);
    setCompleted(c => c + 1);
  }

I modify this code to alert(pending) state variable to test a hypothesis in mind:

async function handleClick() {
    setPending(p => p + 1);
    await delay(3000);

    alert(pending)

    setPending(p => p - 1);
    setCompleted(c => c + 1);
  }

My explanatory attempt :

We learn that React batches multiple set state functions that occur within a single event trigger for its efficiency.

Without an async nature and delay present, for the first click after initial render, the alert() will display 0 since the state variable passed into it isnt the updated value, rather a snapshot of the state at the time of event triggering.

This is due to the fact that the state variable updates only after all code in event trigger function finishes running.

However in cases such as mentioned above, the batching breaks due to the function’s async nature. Specifically in the first setPending() before delay.

Here, the setPending() increments the pending variable and goes ahead and re-renders the component to reflect the changes, hence why we see the number change in the DOM.

After the delay, the alert() displays a surprising 0, along with the visual DOM change of pending and completed due to the followed setPending() that decrements the value and setCompleted().

Due to the batch breaking which re-renders the component and updates pending state variable’s value before the delay, it is expected that alert() receives the updated value.

But.. due to the concept of closure, the event trigger’s function does not have access to the updated values of state variables after the update, rather it owns the value at the start of event trigger, which in this case is initializer 0.

<button> click again will alert() the value 1 instead of actuall updated pending value of 2.

The following mentioned effects can be attributed to JS closure behavior and batch breaking.

Side note: I realllly wish React docs would have “Deep Dived” into this.

Thank you so much !