Does React batch state update functions when using hooks?

ReactjsReact Hooks

Reactjs Problem Overview


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?

Reactjs Solutions


Solution 1 - Reactjs

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.

Solution 2 - Reactjs

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

Solution 3 - Reactjs

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.

Solution 4 - Reactjs

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

Solution 5 - Reactjs

React 18 with createRoot, batches all updates automatically, no matter where they originate from.

Note that React 18 with legacy ReactDOM.render() keeps the old behavior. Use ReactDOM.createRoot() if you want to batch updates inside of timeouts, promises or any other event.

Here we update state twice inside of a timeout, but React renders only once:

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

function App() {
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);

  function handleClick() {
    setTimeout(() => {
      setX((p) => p + 1);
      setY((p) => p + 1);
    }, 100);
  }

  console.log(`render x: ${x} y: ${y}`);

  return (
    <div className="App">
      <button onClick={handleClick}>Update with promise</button>

      <div>X: {x} </div>
      <div>Y: {y} </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionvadirnView Question on Stackoverflow
Solution 1 - ReactjsPatrick HundView Answer on Stackoverflow
Solution 2 - ReactjsShubham KhatriView Answer on Stackoverflow
Solution 3 - ReactjsHalfWebDevView Answer on Stackoverflow
Solution 4 - ReactjsVivek ChauhanView Answer on Stackoverflow
Solution 5 - Reactjsuser9408899View Answer on Stackoverflow