Does React batch state update functions when using hooks?

For class components, this.setState calls batch if inside event handlers. But what happens if state is updated outside the event handler and using useState hook?

function Component() {
  const [a, setA] = useState('a');
  const [b, setB] = useState('b');

  function handleClick() {
    Promise.resolve().then(() => {
      setA('aa');
      setB('bb');
    });
  }

  return <button onClick={handleClick}>{a}-{b}</button>
}

Will it render aa - bb right away? Or it will be aa - b and then aa - bb?


TL;DR – if the state changes are triggered asynchronously (e.g. wrapped in a promise), they will not be batched; if they are triggered directly, they will be batched.

I've set up a sandbox to try this out: https://codesandbox.io/s/402pn5l989

import React, { Fragment, useState } from 'react';
import ReactDOM from 'react-dom';

import './styles.css';

function Component() {
  const [a, setA] = useState('a');
  const [b, setB] = useState('b');
  console.log('a', a);
  console.log('b', b);

  function handleClickWithPromise() {
    Promise.resolve().then(() => {
      setA('aa');
      setB('bb');
    });
  }

  function handleClickWithoutPromise() {
    setA('aa');
    setB('bb');
  }

  return (
    <Fragment>
    <button onClick={handleClickWithPromise}>
      {a}-{b} with promise
    </button>
    <button onClick={handleClickWithoutPromise}>
      {a}-{b} without promise
    </button>
      </Fragment>
  );
}

function App() {
  return <Component />;
}

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);

I've made two buttons, one triggers the state changes wrapped in a promise like in your code example, the other triggers the state changes directly.

If you look at the console, when you hit the button “with promise”, it will first show a aa and b b, then a aa and b bb.

So the answer is no, in this case, it will not render aa - bb right away, each state change triggers a new render, there is no batching.

However, when you click the button “without promise”, the console will show a aa and b bb right away.

So in this case, React does batch the state changes and does one render for both together.


Currently in React v16 and earlier, only updates inside React event handlers such as click or onChange etc are batched by default. So just like classes state updates are batched in a similar way in hooks

There is an unstable API to force batching outside of event handlers for rare cases when you need it.

ReactDOM.unstable_batchedUpdates(() => { ... })

There is a plan to batch all state updates in future version on react probably v17 or above.

Now also if the state update calls from within event handler are in async functions or triggered due to async code they won't be batched where direct updates will be batched

Where without the sync code state updates are batched and async code updates aren't

function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  // async update from useEffect
  useEffect(() => {
    setTimeout(() => {
      setCount1(count => count + 1);
      setCount2(count => count + 2);
    }, 3000);
  }, []);

  const handleAsyncUpdate = async () => {
    await Promise.resolve("state updated");
    setCount1(count => count + 2);
    setCount2(count => count + 1);
  };

  const handleSyncUpdate = () => {
    setCount1(count => count + 2);
    setCount2(count => count + 1);
  };

  console.log("render", count1, count2);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <button type="button" onClick={handleAsyncUpdate}>
        Click for async update
      </button>
      <button type="button" onClick={handleSyncUpdate}>
        Click for sync update
      </button>
    </div>
  );
}

https://codesandbox.io/s/739rqyyqmq


If the event handler is react-based then it batches the updates. This is true for both setState or useState calls.

But it doesn't batch automatically in case the event is non-react based i.e. setTimeout, Promise calls. In short any event from Web APIs.


answer already given by @Patrick Hund .. Just Wanted to update here that with React 18 batch states update is possible for Promise, setTimeout as well by default.

Until React 18, we only batched updates during the React event handlers. Updates inside of promises, setTimeout, native event handlers, or any other event were not batched in React by default.

Check this out for detail explanation . https://github.com/reactwg/react-18/discussions/21