Adam Thompson
Adam Thompson

Reputation: 3506

Are setState updates in async event handlers batched?

From what I understand in react versions 16 (current) and under, setState calls are batched IFF they are made in either component lifecycle events or event handlers. Otherwise, in order to batch calls there is an opt in ReactDOM.unstable_batchedUpdates can be used.

If an event handler is an async function though, the browser will fire the event handler but then a promise will be returned, thus the actual event handler Promise callback won't be run until the next microtasks are picked up in the event loop. In other words, the setState updates do not actually occur in the immediate event handler.

Does this mean that we need to opt into ReactDOM.unstable_batchedUpdates if we want setState updates to be batched in event handlers?

Upvotes: 1

Views: 1411

Answers (2)

Sandy
Sandy

Reputation: 11687

Below call will be batched by React and will cause single re-render.

const onClick = (e) => {
    setHeader('Some Header');
    setTitle('Some Tooltip');
};

Without ReactDOM.unstable_batchedUpdates, React would have made 2 sync calls to re-render components. Now it will have single re-render with this API.

const onClick = (e) => {
    axios.get('someurl').then(response => {
        ReactDOM.unstable_batchedUpdates(() => {
            setHeader('Some Header');
            setTitle('Some Tooltip');
        });
    });
};

Additional Notes:

  1. We used it once in our project and it worked seamless. But I personally prefer to make a state as an object and update things at once rather than this approach. But I understand it is not always possible.
  2. Here we can see that it is in Experimental mode, so not sure if you should use it in production.

Update

Based on the OP comment, below is the version for async await in JavaScript.

const onClick = async e => {

  setState(1);
  setState(2);
  await apiCall();

  ReactDOM.unstable_batchedUpdates(() => {
      setState(3);
      setState(4);
  });
}

The above code will trigger re-render 2 times. Once for update 1/2 and another for 3/4.

Upvotes: 2

Adam Thompson
Adam Thompson

Reputation: 3506

After researching, I believe the answer is that the initial portion of the async event handler (that ends up translating to the executor function of the Promise that is returned underneath the hood) will have setState updates batched, but not anything after any await calls.

This is because everything in the async function body before the first await is translated to the executor function, which is executed within the browser event handler for the event, but everything after ends up as a chained Promise callback for the initial executor function, and these chained callbacks are executed on the microtask queue.

This is all because async () => {} is translated to something like return new Promise().then() where each then is a callback created for code after an await statement.

const onClick = async e => {
  // 1 and 2 will be batched
  setState(1)
  setState(2)

  await apiCall()

  // 3 and 4 will not be batched
  setState(3)
  setState(4)
}

Upvotes: 4

Related Questions