Batching in React
Tags: react, javascript, webdev
Published: 2022-05-01 04:52:30


![Batching In React - Banner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gvfzzv9a79b92qzvliyi.png)
One might think that React's [`useState` hook](https://reactjs.org/docs/hooks-reference.html#usestate) is the simplest hook. Yet, there are some complexities.

## What is batching ?

Batching is when multiple calls to `setState` are grouped into only one state update

```jsx
function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  useEffect(() => {
    // only output once per click
    console.log({ count, flag });
  }, [count, flag]);

  const handleClick = () => {
    // Here, react will re-render only once
    // Hence, the state updates are `batched`
    setCount(c => c + 1);
    setFlag(f => !f);
  };

  return (
    <div className='App'>
      <button onClick={handleClick}>Click Me!</button>
      <h3 style={{ color: flag ? 'blue' : 'black' }}>Count: {count}</h3>
    </div>
  );
}
```

- [✅ See demo (batching inside event handlers)](https://codesandbox.io/s/demonstrating-react-batching-ytdi5s?file=/src/App.js:84-656) (Note on click of button, both count and flag changes, but only one console output)

## Why Batching ?

- Great for performance, since avoids un-necessary re-renders.
- Prevents any component from rendering "half-applied" state updates, which may lead to bugs.

## Inconsistent Batching Behavior

However, React _was_ (more about that later) not consistent about batching. For example, in an [async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) / [promise based API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise),
React would not batch the updates & independent updates would happen (performing two `setState` calls).

```jsx
// little async function
const sleep = () => new Promise(resolve => setTimeout(resolve, 200));

export default function App() {
  const [flag, setFlag] = useState(true);
  const [count, setCount] = useState(0);

  const handleClick = async () => {
    // mimicing some async call
    // (ex, fecthing data from server, etc.)
    await sleep();

    setFlag(f => !f);
    setCount(c => c + 1);
  };

  useEffect(() => {
    // in this case, two console logs can be seen
    // since `setState` is called inside an asynchronous function
    // So,  React would not batch the updates, and perform two independent updates.
    console.log({ count, flag });

    // whenever `flag` or `count` changes, do somethig!
  }, [count, flag]);

  return (
    <>
      <h2>React's Batching Behavior while inside async callbacks</h2>;
      <p>Count: {count}</p>
      <button
        onClick={handleClick}
        style={{ backgroundColor: flag ? 'orange' : 'blue', color: '#fff' }}
      >
        Click me!
      </button>
    </>
  );
}
```

- [⚠️ See demo (not batching updates inside async function)](https://codesandbox.io/s/sleepy-surf-1fk2hb?file=/src/App.js) (Note on click of button, two lines get printed on console)

## Forced batching in async functions

To force `setState` to batch updates out of event handlers, `unstable_batchedUpdates` (an undocumented API) can be used:

```jsx
import { unstable_batchedUpdates } from 'react-dom';

unstable_batchedUpdates(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
});
```

This is because React used to only batch updates during a browser event (like click), but here we're updating the state after the event has already been handled (in aync function):

For demo, see [React 17: forced batching outside of event handlers](https://codesandbox.io/s/reactdom-unstable-batchedupdates-7y4y12?file=/src/App.js)

## Opt out of automatic batching

Some code may depend on reading something from the DOM immediately after a state change. For those use cases, [ReactDOM.flushSync](https://reactjs.org/docs/react-dom.html#flushsync) can be used to opt out of batching

Continuing with our previous example,

```jsx
function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  useEffect(() => {
    console.log({ count, flag });
  }, [count, flag]);

  const handleClick = () => {
    // setCount((c) => c + 1);

    // Force this state update to be synchronous.
    ReactDOM.flushSync(() => setCount(c => c + 1));
    // By this point, DOM is updated.

    setFlag(f => !f);
  };

  return (
    <div className='App'>
      <button onClick={handleClick}>Click Me!</button>
      <h3 style={{ color: flag ? 'blue' : 'black' }}>Count: {count}</h3>
    </div>
  );
}
```

See [⚠️ ReactDOM.flushSync: Opt out of automatic batching in event handlers](https://codesandbox.io/s/opt-out-of-batching-react-17-eq3ydg?file=/src/App.js:118-713)

- However, `ReactDOM.flushSync` is not common & should be sparingly used.

> flushSync flushes the entire tree and actually forces complete re-rendering for updates that happen inside of a call, so you should use it very sparingly. This way it doesn’t break the guarantee of internal consistency between props, state, and refs.

To read more about async behavior of this API & why `setState` is asynchronous, check out this awesome discussion [RFClarification: why is setState asynchronous? #11527](https://github.com/facebook/react/issues/11527#issuecomment-360199710)

## Automatic Batching in React 18

React 18 includes some [out-of-the-box improvements](https://reactjs.org/blog/2022/03/29/react-v18.html#whats-new-in-react-18) with [`ReactDOMClient.createRoot`](https://github.com/reactwg/react-18/discussions/5),
which includes support for automatic batching

> Starting in React 18, all updates will be automatically batched, no matter where they originate from.

So, call to `setState` inside of _event handlers, async functions, timeouts or any function_ will batch automatically (same as inside react events)
This will result in less rendering, and therefore better performance in react applications

```jsx
function handleClick() {
  fetchSomething().then(() => {
    // React 18 and later DOES batch these:
    setCount(c => c + 1);
    setFlag(f => !f);
    // React will only re-render once at the end (that's batching!)
  });
}
```
- Note that this automatic batching behavior will only work in React 18 _with `ReactDOM.createRoot`_
- React 18 with legacy _`ReactDOM.render`_ keeps the old behavior
- To read more about Automatic batching in React 18, see [Automatic batching for fewer renders in React 18 #21](https://github.com/reactwg/react-18/discussions/21)

Want to read it on my blog ? [Checkout this blog post](https://www.shivamjha.io/blog/batching-in-react)