Earlier versions of React batched multiple state updates only inside React event handlers like click or change to avoid multiple re-renders and improve performance.
React 18 adds automatic batching for all use cases to improve performance even further. Now, React batches state updates in React events handlers, promises, setTimeout, native event handlers and so on.
Let's jump into an example to understand different use cases of batching.
In these examples we are assuming that you already replaced render with createRoot API. Automatic batching only works with createRoot API. Please check this discussion to learn more about replacing render with createRoot.
We're using simplest example, also used in the original discussion of this change.
React event handlers
1const App = () => { 2 const [count, setCount] = useState(0); 3 const [flag, setFlag] = useState(false); 4 5 const handleClick = () => { 6 setCount(count + 1); 7 setFlag(!flag); 8 }; 9 10 return ( 11 <div> 12 <button onClick={handleClick}>Click here!</button> 13 <h1>{count}</h1> 14 <h1>{`${flag}`}</h1> 15 </div> 16 ); 17};
Note - React automatically batched all state updates inside event handlers even in previous versions.
After fetch call
1const handleClick = () => { 2 fetch("URL").then(() => { 3 setCount(count + 1); 4 setFlag(!flag); 5 }); 6};
In setTimeout
1const handleClick = () => { 2 setTimeout(() => { 3 setCount(count + 1); 4 setFlag(!flag); 5 }, 1000); 6};
Native event handlers
1const el = document.getElementById("button"); 2el.addEventListener("click", () => { 3 setCount(count + 1); 4 setFlag(!flag); 5});
In each of the above case, both state update calls will be batched by React and performed together at once. This avoids re-rendering component with partially updated state in any event.
In some cases, where we do not wish to batch state updates, we can use flushSync API from react-dom. This is mostly useful when one updated state is required before updating another state.
1import { flushSync } from "react-dom"; 2 3const handleClick = () => { 4 flushSync(() => { 5 setCounter(count + 1); 6 }); 7 8 flushSync(() => { 9 setFlag(!flag); 10 }); 11};
Using flushSync can be used to fix breaking changes when upgrading to React 18. There are chances that in earlier versions of React we might be using one updated state before updating other states. Simple solution is to wrap all such state updates inside individual flushSync API.
For more detailed information on automatic batching, head to this discussion.