Automatic Batching in React 18: What You Should Know

A Deep Dive into React Batching

React uses batching to group state updates within event handlers and inbuilt hooks. It prevents components from re-rendering for each state update and improves application performance.

React 17, and prior versions only support batching for browser events. However, with the React 18 update, it will introduce an improved version of batching called Automatic Batching. This will enable batching for all state updates regardless of where they came from.

Batching In React 17

As mentioned, React 17 only supported batching for browser events and Hooks. Therefore, state updates occurred only with asynchronous operations. Consequently, native event handlers were not batched.

To understand this better, let’s consider the following example of making a state update in a React component:

import { useState } from "react";
import "./App.css";
const App = () => {
  const [additionCount, setAdditionCount] = useState(0);
const [subtractionCount, setSubtractionCount] = useState(0);

console.log("Component Rendering");

const handleOnClick = () => {
setAdditionCount(additionCount + 1);
setSubtractionCount(subtractionCount - 1);
};

return (
<div>
<button style = {{ width: "50%", height: "30%" }}
onClick = {()=>{
handleOnClick();
}}
>
Click Me!
</button><div>
Add Count: {additionCount}
</div>
<div>
Substraction Count: {substractionCount}
</div></div>
);
};
export default App;

In the above example, the handleOnClick() function will be called when a user clicks on the button. It will execute two-state updates on each click.

If you observe the browser console, you will see that the “Component Rendering” message is logged only once for both state updates.

Now you have seen that React batched both state updates and re-rendered the component only once.

But, what if we execute state updates in a context that is not associated with the browser?

For example, consider a fetch() call that asynchronously loads data:

// event handler
const handleOnClickAsync = () => {
fetch(“https://jsonplaceholder.typicode.com/todos/1").then(() => {
setAdditionCount(additionCount + 1);
setSubstractionCount(substractionCount — 1);
});
};

// html
<button style={{ width: “50%”, height: “30%” }}
onClick ={() => {
handleOnClickAsync();
}}
>
Click Me Async Call!
</button>

In the above example, the state update occurs in the callback of fetch() function. If you observe the browser console after executing this example, you will see 2 messages. This indicates that two separate re-renders occur for each state update.

What are the drawbacks to this?

For small applications, this re-rendering process may not impact significantly. But, as you web application grows, the number of nested components will increase. Therefore, if a parent component executes an un-batched state update, the entire component tree will be re-rendered per state update.

This behavior can cause severe performance issues related to application speed. That’s why React introduced Automatic Batching.

Using Automatic Batching

React v18 ensures that state updates invoked from any location will be batched by default. This will batch state updates, including native event handlers, asynchronous operations, timeouts, and intervals.

Let’s consider a simple React application to understand how automatic batching works.

To begin with, let’s create a React project with the latest beta version of React 18 (npm install react@beta react-dom@beta).

Afterwards, the index.js file must be updated to use the createRoot() API for enabling all React 18 features to use Automatic Batching.

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
const container = document.getElementById("root");
// Create a root.
const root = ReactDOM.createRoot(container);
// Render the top component to the root.
root.render(<App />);
reportWebVitals();

Now, let’s update the App.js file with 3 event listeners where one event listener contains state updates invoked using:

  • Event Handlers
  • Asynchronous Operations
  • Timeouts
import logo from "./logo.svg";
import "./App.css";
import { useState } from "react";
const App = () => {
const [count, setCount] = useState(0);
const [clicked, setClicked] = useState(false);
console.log("React 18 Application Re-Rendering");
// click event
const handleClick = () => {
// 1 Re-Render
setClicked(!clicked);setCount(count + 1);
};

// async operation
const handleAsyncClick = () => {
fetch("https://jsonplaceholder.typicode.com/todos/1").then(() => {
// trigger 1 re-render due to React 18 Improved Batching
setClicked(!clicked);
setCount(count + 1);
});
};

// timeout/interval
const handleTimeOutClick = () => {
setTimeout(() => {
// trigger 1 re-render due to React 18 Improved Batching
setClicked(!clicked);
setCount(count + 1); 
});
};

return (
<div className="App">
 <header className="App-header">
<div> Count: {count} </div>
<div> Clicked: {clicked} </div>
<button onClick={handleClick}> Event Handler </button>
 <button onClick={handleAsyncClick}> Async Handler </button>
<button onClick={handleTimeOutClick}> Timeout Handler </button>
</header>
</div>
);
};

export default App;

When all three buttons are clicked, three logs are printed in the browser console, although each event handler has two state updates occurring in it.

The above example shows an optimal re-rendering process where each event causes only one re-render as React batches all state updates regardless of the invocation location.

As you can see, the way React 18 handles component re-renders is an improvement. React 17 has a gap where non-browser events are not batched. React 18 fills in that gap and improves application performance by reducing the unnecessary re-renders.

How To Stop Automatic Batching?

Automatic batching is indeed an amazing feature. But, there can be situations where we need to prevent this from happening. For that, React provides a method named flushSync() in react-dom that allows us to trigger a re-render for a specific state update.

How Does Flush Sync Work?

To use this, simply import it from react-dom using the syntax:

import { flushSync } from 'react-dom';

And then, call the method inside an event handler and place your state update inside the body of flushSync().

const handleClick = () => {
flushSync(() => {
setClicked(!clicked);
// react will create a re-render here
});

setCount(count + 1);
// react will create a re-render here
};

When the event is invoked, React will update the DOM once at flushSync() and update the DOM again at setCount(count + 1) avoiding the batching.

Unlock 10x development with independent components

Building monolithic apps means all your code is internal and is not useful anywhere else. It just serves this one project. And as you scale to more code and people, development becomes slow and painful as everyone works in one codebase and on the same version.

But what if you build independent components first, and then use them to build any number of projects? You could accelerate and scale modern development 10x.

OSS Tools like Bit offer a powerful developer experience for building independent components and composing modular applications. Many teams start by building their Design Systems or Micro Frontends, through independent components. Give it a try →

An independent product component: watch the auto-generated dependency graph

Conclusion

Automatic batching is one of the most anticipated features of React 18 release. This article has provided a detailed view of issues with the correct batching process and how automatic batching will resolve them.

You can find the complete example of the above code in my GitHub repository. I hope you have found this article useful. Thank you for reading.

Learn More


Automatic Batching in React 18: What You Should Know was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Bits and Pieces - Medium and was authored by Lakindu Hewawasam

A Deep Dive into React Batching

React uses batching to group state updates within event handlers and inbuilt hooks. It prevents components from re-rendering for each state update and improves application performance.

React 17, and prior versions only support batching for browser events. However, with the React 18 update, it will introduce an improved version of batching called Automatic Batching. This will enable batching for all state updates regardless of where they came from.

Batching In React 17

As mentioned, React 17 only supported batching for browser events and Hooks. Therefore, state updates occurred only with asynchronous operations. Consequently, native event handlers were not batched.

To understand this better, let’s consider the following example of making a state update in a React component:

import { useState } from "react";
import "./App.css";
const App = () => {
  const [additionCount, setAdditionCount] = useState(0);
const [subtractionCount, setSubtractionCount] = useState(0);

console.log("Component Rendering");

const handleOnClick = () => {
setAdditionCount(additionCount + 1);
setSubtractionCount(subtractionCount - 1);
};

return (
<div>
<button style = {{ width: "50%", height: "30%" }}
onClick = {()=>{
handleOnClick();
}}
>
Click Me!
</button><div>
Add Count: {additionCount}
</div>
<div>
Substraction Count: {substractionCount}
</div></div>
);
};
export default App;

In the above example, the handleOnClick() function will be called when a user clicks on the button. It will execute two-state updates on each click.

If you observe the browser console, you will see that the "Component Rendering" message is logged only once for both state updates.

Now you have seen that React batched both state updates and re-rendered the component only once.

But, what if we execute state updates in a context that is not associated with the browser?

For example, consider a fetch() call that asynchronously loads data:

// event handler
const handleOnClickAsync = () => {
fetch(“https://jsonplaceholder.typicode.com/todos/1").then(() => {
setAdditionCount(additionCount + 1);
setSubstractionCount(substractionCount — 1);
});
};

// html
<button style={{ width: “50%”, height: “30%” }}
onClick ={() => {
handleOnClickAsync();
}}
>
Click Me Async Call!
</button>

In the above example, the state update occurs in the callback of fetch() function. If you observe the browser console after executing this example, you will see 2 messages. This indicates that two separate re-renders occur for each state update.

What are the drawbacks to this?

For small applications, this re-rendering process may not impact significantly. But, as you web application grows, the number of nested components will increase. Therefore, if a parent component executes an un-batched state update, the entire component tree will be re-rendered per state update.

This behavior can cause severe performance issues related to application speed. That’s why React introduced Automatic Batching.

Using Automatic Batching

React v18 ensures that state updates invoked from any location will be batched by default. This will batch state updates, including native event handlers, asynchronous operations, timeouts, and intervals.

Let’s consider a simple React application to understand how automatic batching works.

To begin with, let’s create a React project with the latest beta version of React 18 (npm install react@beta react-dom@beta).

Afterwards, the index.js file must be updated to use the createRoot() API for enabling all React 18 features to use Automatic Batching.

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
const container = document.getElementById("root");
// Create a root.
const root = ReactDOM.createRoot(container);
// Render the top component to the root.
root.render(<App />);
reportWebVitals();

Now, let’s update the App.js file with 3 event listeners where one event listener contains state updates invoked using:

  • Event Handlers
  • Asynchronous Operations
  • Timeouts
import logo from "./logo.svg";
import "./App.css";
import { useState } from "react";
const App = () => {
const [count, setCount] = useState(0);
const [clicked, setClicked] = useState(false);
console.log("React 18 Application Re-Rendering");
// click event
const handleClick = () => {
// 1 Re-Render
setClicked(!clicked);setCount(count + 1);
};

// async operation
const handleAsyncClick = () => {
fetch("https://jsonplaceholder.typicode.com/todos/1").then(() => {
// trigger 1 re-render due to React 18 Improved Batching
setClicked(!clicked);
setCount(count + 1);
});
};

// timeout/interval
const handleTimeOutClick = () => {
setTimeout(() => {
// trigger 1 re-render due to React 18 Improved Batching
setClicked(!clicked);
setCount(count + 1); 
});
};

return (
<div className="App">
 <header className="App-header">
<div> Count: {count} </div>
<div> Clicked: {clicked} </div>
<button onClick={handleClick}> Event Handler </button>
 <button onClick={handleAsyncClick}> Async Handler </button>
<button onClick={handleTimeOutClick}> Timeout Handler </button>
</header>
</div>
);
};

export default App;

When all three buttons are clicked, three logs are printed in the browser console, although each event handler has two state updates occurring in it.

The above example shows an optimal re-rendering process where each event causes only one re-render as React batches all state updates regardless of the invocation location.

As you can see, the way React 18 handles component re-renders is an improvement. React 17 has a gap where non-browser events are not batched. React 18 fills in that gap and improves application performance by reducing the unnecessary re-renders.

How To Stop Automatic Batching?

Automatic batching is indeed an amazing feature. But, there can be situations where we need to prevent this from happening. For that, React provides a method named flushSync() in react-dom that allows us to trigger a re-render for a specific state update.

How Does Flush Sync Work?

To use this, simply import it from react-dom using the syntax:

import { flushSync } from 'react-dom';

And then, call the method inside an event handler and place your state update inside the body of flushSync().

const handleClick = () => {
flushSync(() => {
setClicked(!clicked);
// react will create a re-render here
});

setCount(count + 1);
// react will create a re-render here
};

When the event is invoked, React will update the DOM once at flushSync() and update the DOM again at setCount(count + 1) avoiding the batching.

Unlock 10x development with independent components

Building monolithic apps means all your code is internal and is not useful anywhere else. It just serves this one project. And as you scale to more code and people, development becomes slow and painful as everyone works in one codebase and on the same version.

But what if you build independent components first, and then use them to build any number of projects? You could accelerate and scale modern development 10x.

OSS Tools like Bit offer a powerful developer experience for building independent components and composing modular applications. Many teams start by building their Design Systems or Micro Frontends, through independent components. Give it a try →

An independent product component: watch the auto-generated dependency graph

Conclusion

Automatic batching is one of the most anticipated features of React 18 release. This article has provided a detailed view of issues with the correct batching process and how automatic batching will resolve them.

You can find the complete example of the above code in my GitHub repository. I hope you have found this article useful. Thank you for reading.

Learn More


Automatic Batching in React 18: What You Should Know was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Bits and Pieces - Medium and was authored by Lakindu Hewawasam


Print Share Comment Cite Upload Translate Updates
APA

Lakindu Hewawasam | Sciencx (2022-02-01T10:28:01+00:00) Automatic Batching in React 18: What You Should Know. Retrieved from https://www.scien.cx/2022/02/01/automatic-batching-in-react-18-what-you-should-know/

MLA
" » Automatic Batching in React 18: What You Should Know." Lakindu Hewawasam | Sciencx - Tuesday February 1, 2022, https://www.scien.cx/2022/02/01/automatic-batching-in-react-18-what-you-should-know/
HARVARD
Lakindu Hewawasam | Sciencx Tuesday February 1, 2022 » Automatic Batching in React 18: What You Should Know., viewed ,<https://www.scien.cx/2022/02/01/automatic-batching-in-react-18-what-you-should-know/>
VANCOUVER
Lakindu Hewawasam | Sciencx - » Automatic Batching in React 18: What You Should Know. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/02/01/automatic-batching-in-react-18-what-you-should-know/
CHICAGO
" » Automatic Batching in React 18: What You Should Know." Lakindu Hewawasam | Sciencx - Accessed . https://www.scien.cx/2022/02/01/automatic-batching-in-react-18-what-you-should-know/
IEEE
" » Automatic Batching in React 18: What You Should Know." Lakindu Hewawasam | Sciencx [Online]. Available: https://www.scien.cx/2022/02/01/automatic-batching-in-react-18-what-you-should-know/. [Accessed: ]
rf:citation
» Automatic Batching in React 18: What You Should Know | Lakindu Hewawasam | Sciencx | https://www.scien.cx/2022/02/01/automatic-batching-in-react-18-what-you-should-know/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.