Batching in React
by Sujay Prabhu, Senior System Analyst
React 18 is coming up with Automatic Batching
for fewer renders along with new other features like SSR support for Suspense.
Support for concurrent features will help in improving user experience.
Batching was present in React from the previous versions as well. But, with the introduction of Automatic Batching, there will be uniformity in the behaviour of re-rendering.
In React, components re-render if the value of state or props is altered.
State updates can be scheduled using setState
in case of Class components.
In Functional components, state values can be updated using the function returned by useState
.
React performs Batching while updating component state for performance improvement.
Batching means grouping multiple state updates into a single re-render.
Let us see how Batching used to work before v18
and what changes have been brought in v18
.
Batching before React 18
React, by default performs batch updates only inside event-handlers.
So, setState is asynchronous only inside event handlers. But, synchronous inside of async functions like Promises, setTimeout
class App extends React.Component {
constructor() {
super();
this.state = {
name: 'setState',
};
this.handleClickSync = this.handleClickSync.bind(this);
this.handleClickAsync = this.handleClickAsync.bind(this);
}
handleClickSync() {
Promise.resolve().then(() => {
this.setState({ name: 'sync' });
console.log('state', this.state.name); // sync
});
}
handleClickAsync() {
this.setState({ name: 'Async' });
console.log('state', this.state.name); // sync (value of previous state)
// after re-render state value will be `Async`
}
render() {
return (
<div>
<h1 onClick={this.handleClickSync}>Sync setState</h1>
<h1 onClick={this.handleClickAsync}>Async setState</h1>
</div>
);
}
}
If n
state updates were present in async functions, React re-renders component n
number of times, updating one state per render.
const App = () => {
const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
const handleClickWithBatching = () => {
setCounter1((count) => count + 1);
setCounter2((count) => count + 1);
};
const handleClickWithoutBatching = () => {
Promise.resolve().then(() => {
setCounter1((count) => count + 1);
setCounter2((count) => count + 1);
});
};
console.log('counters', counter1, counter2);
/*
On click of Single re-render
conuters: 1 1
On click of Multiple re-render
conuters: 2 1
counters: 2 2
*/
return (
<div className="App">
<h2 onClick={handleClickWithBatching}>Single Re-render</h2>
<h2 onClick={handleClickWithoutBatching}>Multiple Re-render</h2>
</div>
);
};
However, forced batching can be implemented with the help of an unstable API ReactDOM.unstable_batchedUpdates
import { unstable_batchedUpdates } from 'react-dom';
const handleClickWithoutBatching = () => {
Promise.resolve().then(() => {
unstable_batchedUpdates(() => {
setCounter1((count) => count + 1);
setCounter2((count) => count + 1);
});
});
};
/*
On click of Single re-render
conuters: 1 1
On click of Multiple re-render
counters: 2 2
*/
Note: The API is unstable in the sense that React might remove this API once uniformity is brought in the functionality of Batching
Batching in React 18
React 18 performs automatic batching with the help of createRoot
API.
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
setState is asynchronous even inside async fucntions.
Batching will be done throughout the components similarly irrespective of its origin.
const handleClick = () => {
setCounter1((count) => count + 1);
setCounter2((count) => count + 1);
};
const handleClick = () => {
Promise.resolve().then(() => {
setCounter1((count) => count + 1);
setCounter2((count) => count + 1);
});
};
//In both cases, component will be re-rendered only once
One can opt out of Batching with the help of ReactDOM.flushSync
import { flushSync } from 'react-dom';
const handleClick = () => {
flushSync(() => {
setCounter1((count) => count + 1);
});
flushSync(() => {
setCounter2((count) => count + 1);
});
};
ReactDOM.unstable_batchedUpdates
API still exists in React 18, but it might be removed in the coming major versions.
Happy Learning