React 18 introduced a new root API called createRoot(). It replaces the legacy render() method and unlocks concurrent rendering, better scheduling, and modern performance optimizations.
This method was used in React 17 and below. It renders synchronously and does not support concurrent features.
// React 17 and below rendering API
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
React 18 applications should use createRoot() from react-dom/client. This enables concurrent rendering.
// React 18+ concurrent root API
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const container = document.getElementById("root");
const root = createRoot(container);
root.render(<App />);
// Migration from render() to createRoot()
// ❌ Old API
ReactDOM.render(<App />, document.getElementById("root"));
// ✅ New API
const root = createRoot(document.getElementById("root"));
root.render(<App />);
react-dom/clientroot.render()This demo visualizes the performance difference. We will trigger 3 state updates in a row. Watch how many times the component "re-renders" (flashes).
render()React 17 Behavior
createRoot()React 18 Automatic Batching
// useTransition works only with createRoot()
import React, { useState, useTransition } from "react";
function Demo() {
const [text, setText] = useState("");
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
startTransition(() => {
setText(e.target.value);
});
};
return (
<div>
<input type="text" onChange={handleChange} />
<p>{isPending ? "Loading..." : text}</p>
</div>
);
}
export default Demo;
This example demonstrates concurrent rendering using useTransition(), which requires createRoot().
createRoot() in React 18+hydrateRoot() for SSRStrictMode during developmentrender() with createRoot()useTransition() for performance testing