React 18 โ What’s New
React 18 introduced a new concurrent rendering engine without breaking existing apps. Here are the most important changes.
New Root API
The old ReactDOM.render() is deprecated. Replace it with createRoot():
// โ Old (React 17 and below)
import ReactDOM from "react-dom";
ReactDOM.render(<App />, document.getElementById("root"));
// โ
New (React 18+)
import ReactDOM from "react-dom/client";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
Automatic Batching
In React 17, state updates inside event handlers were batched, but updates inside setTimeout, Promises, or native events were not. React 18 batches all updates automatically.
// React 18: both setCount and setFlag are batched into ONE re-render
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// Only one re-render โ automatic batching!
}, 1000);
Transitions
Mark non-urgent state updates as transitions so React can deprioritise them and keep the UI responsive.
import { startTransition } from "react";
function SearchPage() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
function handleInput(e) {
// Urgent: update the input immediately
setQuery(e.target.value);
// Non-urgent: defer the heavy search results update
startTransition(() => {
setResults(searchDatabase(e.target.value));
});
}
return <input value={query} onChange={handleInput} />;
}
Suspense for Data Fetching
React 18 extends Suspense to work with data fetching frameworks (Next.js, Relay, React Query). The component renders a fallback while data loads.
function App() {
return (
<Suspense fallback={<Spinner />}>
<UserProfile /> {/* Shows <Spinner /> until UserProfile's data is ready */}
</Suspense>
);
}
NoteUpgrading from React 17 to 18 requires only two changes: update
react and react-dom to ^18.0.0, and switch to the new createRoot API.