createRoot

Creates a React root for a browser DOM node so you can display React components inside it.

Signature
const root = createRoot(domNode, options?)
  • Added in React 18.0.0

Parameters

ParameterTypeDescription
$domNodeElementA DOM element. React creates a root for this element and takes ownership of its contents.
$optionsoptionalobjectOptional configuration for the root.
$options.onCaughtErroroptional(error, errorInfo) => voidCalled when React catches an error inside an Error Boundary. Receives the error and an errorInfo object containing componentStack.
$options.onUncaughtErroroptional(error, errorInfo) => voidCalled when an error is thrown and not caught by any Error Boundary. Receives the error and an errorInfo object containing componentStack.
$options.onRecoverableErroroptional(error, errorInfo) => voidCalled when React automatically recovers from an error. Some recoverable errors expose the original cause as error.cause.
$options.identifierPrefixoptionalstringA prefix React uses for IDs generated by useId. Useful to avoid collisions when several roots share a page.

Return value

RootA root object with two methods: render and unmount.

Errors

Throws if domNode is not a DOM element, and warns if you call createRoot twice on the same container without unmounting the first root.

Changelog

  • PHP 18.0.0Introduced as the replacement for ReactDOM.render; roots created this way opt the tree into concurrent features such as transitions and Suspense streaming.
  • PHP 19.0.0Added the onUncaughtError and onCaughtError options, and stopped duplicating uncaught errors in the console — errors are now reported once through these handlers.

createRoot is the boundary between plain DOM and React. You hand it an element, and from that moment React owns everything inside it: the first render wipes the node's existing children and replaces them with your tree.

When to reach for it

  • The entry file of a client-rendered app — usually one call, against document.getElementById('root').
  • Embedding a React widget into a page owned by something else (a CMS template, a legacy jQuery app, a browser extension). Each widget gets its own root and they are fully independent: separate state, separate re-renders.

If the markup inside the container was produced by a server render or a static build, do not use createRoot — it throws that HTML away and re-renders from scratch, losing the performance you paid for. Use hydrateRoot instead.

What trips people up

Two roots never share context, state, or batching. A provider mounted in root A is invisible to root B, so a modal rendered through a second root cannot read your theme context. When you want a component to render into a different DOM node but stay inside the same tree, that is createPortal, not a second root.

Also pass identifierPrefix when a page hosts several roots, or useId can generate colliding IDs across them.

Example

Try it yourself
Loading editor…
Loading React…

When to use it

  • Bootstrapping a client-rendered single-page app from main.jsx
  • Mounting an independent React widget into a page rendered by a non-React backend
  • Rendering React into a container you created yourself, such as a portal host or a browser-extension overlay
  • Giving each root its own identifierPrefix so useId values stay unique across widgets

Frequently asked questions

What replaced ReactDOM.render in React 18?

createRoot from react-dom/client replaced ReactDOM.render. Call createRoot(container) once, keep the returned root, and call root.render(<App />) on it; the old ReactDOM.render API was removed in React 19.

Can I call createRoot on the same element twice?

No — React warns and you end up with two roots overwriting each other's DOM. Keep the first root object and call root.render again, or call root.unmount() before creating a replacement root on that node.

Do I need createRoot if I use Next.js or Remix?

No, the framework calls it (or hydrateRoot) for you in its own entry code. You only call createRoot directly when you control the page's entry point yourself, such as in a Vite app or an embedded widget.

More examples

The standard application entry point

The whole app lives under a single root. The <div id="root"></div> in index.html is empty, so nothing is discarded when render runs. StrictMode is a component you pass to render, not an option on createRoot — it double-invokes renders and effects in development to surface impure components.

Example · jsx
// main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";

const container = document.getElementById("root");
if (!container) {
  throw new Error('No #root element in index.html');
}

const root = createRoot(container);
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

Reporting errors through the root options

All three callbacks receive an errorInfo object whose componentStack names the components between the root and the throw site. The split matters for triage: onCaughtError means a boundary showed a fallback and the user saw something, onUncaughtError means the tree unmounted, and onRecoverableError usually means a hydration or render retry that the user never noticed.

Example · jsx
import { createRoot } from "react-dom/client";
import App from "./App.jsx";

const root = createRoot(document.getElementById("root"), {
  identifierPrefix: "app-",
  onUncaughtError(error, errorInfo) {
    // No Error Boundary handled this — the tree was torn down.
    report("fatal", error, errorInfo.componentStack);
  },
  onCaughtError(error, errorInfo) {
    // An Error Boundary rendered its fallback instead.
    report("handled", error, errorInfo.componentStack);
  },
  onRecoverableError(error, errorInfo) {
    // React fixed it itself, e.g. by re-rendering on the client.
    report("recovered", error.cause ?? error, errorInfo.componentStack);
  },
});

function report(level, error, componentStack) {
  console.log(level, error.message, componentStack);
}

root.render(<App />);

Two independent roots do not share context

The first root prints "theme: dark"; the second prints "theme: light", the context default, because context travels through the React tree and not through the DOM. If the sidebar needs the same theme, either render it inside the first tree with createPortal or repeat the provider in the second root.

Example · jsx
import { createContext, useContext } from "react";
import { createRoot } from "react-dom/client";

const ThemeContext = createContext("light");

function Label() {
  return <span>theme: {useContext(ThemeContext)}</span>;
}

createRoot(document.getElementById("main")).render(
  <ThemeContext value="dark">
    <Label />
  </ThemeContext>
);

// A separate root. The provider above is not an ancestor here.
createRoot(document.getElementById("sidebar")).render(<Label />);

Discussion

  • Be the first to comment on this lesson.