# createRoot in React 18: The Complete Migration Guide to Concurrent Rendering

> Unlock React 18 concurrency with createRoot. This guide explains its role in managing Fiber trees and enabling new features, replacing ReactDOM render.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: migration-guide
- Published: 2026-02-20

---

**`createRoot` is the mandatory entry point for React 18's Concurrent rendering pipeline that creates a persistent root object to manage the internal Fiber tree, enabling time-slicing, Suspense boundaries, and interruptible rendering while replacing the legacy `ReactDOM.render()` API.**

When migrating a React application from the legacy ReactDOM API to modern concurrent features, understanding `createRoot` is essential. According to the facebook/react source code, this function fundamentally rearchitects how React attaches to the DOM by establishing a persistent container in [`packages/react-dom/src/client/ReactDOMRoot.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/client/ReactDOMRoot.js) that owns its own scheduling lifecycle and rendering state.

## What createRoot Does in React 18

Unlike the legacy `ReactDOM.render()` which immediately mounted a component, `createRoot` establishes a **persistent root object** (`ReactDOMRoot`) that acts as the gateway to React's concurrent capabilities. This object maintains the Fiber tree, manages rendering priorities, and provides the `render()` and `unmount()` methods for lifecycle control.

The function performs six critical operations defined in the React source:

- **Validates the DOM container** using `isValidContainer` to ensure the target is a proper DOM element ([`packages/react-dom/src/client/ReactDOMRoot.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/client/ReactDOMRoot.js) lines 71-78)
- **Configures Concurrent Mode** by setting the `ConcurrentRoot` flag, which enables time-slicing and interruptible rendering (lines 98-101)
- **Initializes the Fiber root** via `createContainer`, establishing the internal tree structure with options for strict mode and error handling (lines 136-149)
- **Marks the container** using `markContainerAsRoot` to prevent accidental double-mounting and identify React-managed nodes (lines 149-151)
- **Wires the event system** through `listenToAllSupportedEvents`, attaching React's synthetic event delegation to the container or its parent (lines 253-256)
- **Returns the Root object** exposing `root.render()` to schedule updates via `updateContainer` (lines 105-111) and `root.unmount()` to cleanup via `updateContainerSync` and `flushSyncWork` (lines 138-146)

## Migrating from ReactDOM.render to createRoot

The migration requires changing from the imperative legacy API to the new root-based pattern. Instead of calling `ReactDOM.render` directly, you create a root once and call render on that object.

Replace your legacy entry point:

```javascript
import ReactDOM from 'react-dom';
import App from './App';

// Legacy API - immediately renders
ReactDOM.render(<App />, document.getElementById('root'));

```

With the modern concurrent approach:

```javascript
import { createRoot } from 'react-dom/client';
import App from './App';

const container = document.getElementById('root');

// Create a persistent concurrent root
const root = createRoot(container);

// Schedule the initial render
root.render(<App />);

```

## Configuring Error Boundaries and Strict Mode

The `createRoot` function accepts an options object as its second parameter, allowing configuration of error handling and development behaviors. These options are passed through to `createContainer` during Fiber root initialization.

```javascript
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'), {
  // Enable new Strict Mode behaviors for concurrent features
  unstable_strictMode: true,

  // Handle uncaught errors from the Fiber tree
  onUncaughtError: (error, errorInfo) => {
    console.error('Uncaught React error:', error, errorInfo.componentStack);
  },

  // Track transition states
  onDefaultTransitionIndicator: (isPending) => {
    console.log('Transition status:', isPending ? 'pending' : 'complete');
  }
});

root.render(<App />);

```

## Hydrating Server-Rendered Markup

For server-side rendered applications, use `hydrateRoot` instead of `createRoot`. This companion function creates a concurrent root while hydrating existing HTML markup, maintaining the same internal architecture but attaching to pre-rendered content.

```javascript
import { hydrateRoot } from 'react-dom/client';
import App from './App';

const container = document.getElementById('root');

// Creates concurrent root while hydrating SSR markup
const root = hydrateRoot(container, <App />, {
  unstable_strictMode: true
});

// No need to call root.render() - hydration occurs immediately

```

## Summary

- **`createRoot` replaces `ReactDOM.render`** as the entry point for React 18 applications, returning a persistent `ReactDOMRoot` object rather than mounting immediately.
- **Always creates a ConcurrentRoot**, enabling time-slicing, Suspense, and interruptible rendering regardless of whether you use concurrent features explicitly.
- **Validates and marks containers** to prevent double-mounting and ensure DOM element compatibility through `isValidContainer` and `markContainerAsRoot`.
- **Initializes the Fiber tree** via `createContainer` in [`packages/react-dom/src/client/ReactDOMRoot.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/client/ReactDOMRoot.js), configuring error boundaries and strict mode options.
- **Exposes `render()` and `unmount()` methods** that schedule updates through the concurrent reconciler rather than synchronous rendering.
- **Wires React's event system** automatically via `listenToAllSupportedEvents`, setting up synthetic event delegation on the container.

## Frequently Asked Questions

### What happens if I continue using ReactDOM.render in React 18?

React 18 will operate in **legacy mode**, disabling all concurrent features including automatic batching, Suspense improvements, and transitions. The console will display a deprecation warning urging migration to `createRoot`, and you will not benefit from the performance optimizations available in the new rendering pipeline.

### Can I use createRoot with React 17 or earlier?

No, `createRoot` is exclusively available in **React 18 and later**. Attempting to import it from `react-dom/client` in earlier versions will throw a module not found error. The ConcurrentRoot flag and Fiber architecture changes required for `createRoot` functionality were introduced specifically in the React 18 release.

### What is the difference between createRoot and hydrateRoot?

While both create concurrent roots, **`createRoot`** is for client-side rendered applications calling `root.render()` to mount to an empty DOM container, whereas **`hydrateRoot`** is for server-side rendered applications where HTML already exists. `hydrateRoot` accepts the initial React element as its second argument and performs hydration immediately without requiring a separate `render()` call.

### How do I properly unmount a React 18 application?

Call the **`unmount()`** method on the root object returned by `createRoot`. This triggers `updateContainerSync` and `flushSyncWork` to cleanly destroy the Fiber tree, detach event listeners, and unmark the container. Unlike the legacy `ReactDOM.unmountComponentAtNode()`, this method is instance-specific and handles cleanup through the concurrent scheduler.