What’s New in React 18? A Deep Dive into Concurrent Rendering and Suspense
This article explains how React 18 introduces progressive upgrades, a new Root API, automatic batching, the flushSync utility, an enhanced Suspense SSR architecture, and the startTransition API, providing code examples and practical guidance for developers transitioning from earlier React versions.
React 15 to 16 introduced many breaking changes, making upgrades painful; developers stayed on v16 while new capabilities were added in incremental releases such as 16.3, 16.8 and 16.12.
After two and a half years of v16, the React team released v17 as a transition version aimed at reducing future upgrade costs. The key change was moving event delegation from the document to the root DOM container of each React tree, allowing multiple React versions to coexist.
React 18 continues the “progressive upgrade” strategy: you can upgrade with minimal or no code changes, and new features like concurrent rendering are optional and non‑breaking.
New Root API
In React 18, ReactDOM.render() is deprecated (legacy) and replaced by ReactDOM.createRoot(). The two APIs differ as follows:
import ReactDOM from 'react-dom';
import App from 'App';
// Legacy API
ReactDOM.render(<App />, document.getElementById('root')); import ReactDOM from 'react-dom';
import App from 'App';
// New Root API
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);The new API enables creating multiple root nodes and, in the future, using different React versions for each.
Automatic Batching
Batching groups multiple state updates into a single re‑render for better performance. React 17 only batches updates that occur during a browser event; React 18 also batches updates that happen after the event (e.g., in async callbacks).
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClickPrev() {
setCount(c => c - 1); // not rendered yet
setFlag(f => !f); // not rendered yet
// React will re‑render once at the end (batching)
}
function handleClickNext() {
fetchSomething().then(() => {
// In React 17 these updates are NOT batched
setCount(c => c + 1);
setFlag(f => !f);
});
}
return (
<div>
<button onClick={handleClickPrev}>Prev</button>
<button onClick={handleClickNext}>Next</button>
<h1 style={{color: flag ? "blue" : "black"}}>{count}</h1>
</div>
);
}If you need an immediate DOM update (e.g., to read the new layout), use ReactDOM.flushSync():
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// DOM is now updated
flushSync(() => {
setFlag(f => !f);
});
}Impact on Hooks and Classes
Hooks are unaffected.
Class components are mostly unaffected; the difference appears when reading state between two setState calls.
When necessary, ReactDOM.flushSync() can be used to force synchronous updates in class components.
New Suspense SSR Architecture
React 18 introduces a streaming HTML approach combined with selective hydration. The server streams the initial HTML without waiting for slow components (e.g., <Comments>), inserting a placeholder such as a spinner. Once the data arrives, React injects the component’s HTML via a tiny inline
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
