Skip to main content

createRoot

Creates a React root for rendering React components into a DOM container with concurrent rendering features.
import { createRoot } from 'react-dom/client';

const root = createRoot(container, options);

Reference

createRoot(container, options?)

Call createRoot to create a React root for displaying content inside a browser DOM element.
/src/client/ReactDOMRoot.js:171-260
const root = createRoot(document.getElementById('root'));
root.render(<App />);

Parameters

container
Element | Document | DocumentFragment
required
A DOM element, Document, or DocumentFragment that will contain your React app. React will create a root for this container and manage the DOM inside it.Validation:
  • Must be a valid DOM container
  • Cannot be a text node or comment node (unless feature flag enabled)
  • Should not be already managed by another React root
options
CreateRootOptions
Optional configuration object for the root.

Returns

root
RootType
A root object with two methods: render and unmount.

root.render(reactNode)

Renders a React component into the root’s DOM container.
/src/client/ReactDOMRoot.js:107-136
root.render(<App />);

Parameters

reactNode
ReactNode
required
A React node to render. Usually JSX like <App />, but can also be a React element created with createElement(), a string, a number, null, or undefined.

Notes

  • The first call replaces any existing HTML inside the container
  • Subsequent calls use React’s diffing algorithm to update efficiently
  • Does not support callback argument (removed in React 18)
  • Updates are batched and may be asynchronous

root.unmount()

Destroys a rendered React component and cleans up its DOM and event handlers.
/src/client/ReactDOMRoot.js:139-169
root.unmount();

Notes

  • Runs cleanup functions in useEffect and useLayoutEffect
  • Synchronously completes unmounting before returning
  • Should not be called while React is already rendering
  • Container is emptied but not removed from the DOM

Usage Examples

Basic Client Rendering

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

const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);

With Error Handling

import { createRoot } from 'react-dom/client';
import * as Sentry from '@sentry/react';

const root = createRoot(document.getElementById('root'), {
  onRecoverableError: (error, errorInfo) => {
    // Log recoverable errors (hydration mismatches, etc.)
    console.warn('Recoverable error:', error);
    Sentry.captureException(error, {
      contexts: {
        react: {
          componentStack: errorInfo.componentStack,
        },
      },
    });
  },
  onUncaughtError: (error, errorInfo) => {
    // Log uncaught errors
    Sentry.captureException(error, {
      contexts: {
        react: {
          componentStack: errorInfo.componentStack,
        },
      },
    });
  },
  onCaughtError: (error, errorInfo) => {
    // Log errors caught by error boundaries
    console.log('Caught by error boundary:', errorInfo.errorBoundary);
  },
});

root.render(<App />);

Multiple Roots on Same Page

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

// Create separate roots with unique ID prefixes
const headerRoot = createRoot(document.getElementById('header'), {
  identifierPrefix: 'header-',
});

const sidebarRoot = createRoot(document.getElementById('sidebar'), {
  identifierPrefix: 'sidebar-',
});

const mainRoot = createRoot(document.getElementById('main'), {
  identifierPrefix: 'main-',
});

headerRoot.render(<Header />);
sidebarRoot.render(<Sidebar />);
mainRoot.render(<MainContent />);

Updating the Root

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

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

// Initial render
root.render(<App initialCount={0} />);

// Later update - React will diff and update efficiently
setTimeout(() => {
  root.render(<App initialCount={5} />);
}, 1000);

Cleaning Up

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

const root = createRoot(document.getElementById('root'));
root.render(<App />);

// Clean up when done (e.g., in SPA navigation)
function cleanup() {
  root.unmount();
  // Container is now empty and ready for reuse
}

// Call cleanup when navigating away or app unmounts
window.addEventListener('beforeunload', cleanup);

TypeScript

import { createRoot, type Root } from 'react-dom/client';

interface CreateRootOptions {
  unstable_strictMode?: boolean;
  unstable_transitionCallbacks?: TransitionTracingCallbacks;
  identifierPrefix?: string;
  onUncaughtError?: (
    error: unknown,
    errorInfo: { componentStack?: string }
  ) => void;
  onCaughtError?: (
    error: unknown,
    errorInfo: {
      componentStack?: string;
      errorBoundary?: React.Component;
    }
  ) => void;
  onRecoverableError?: (
    error: unknown,
    errorInfo: { componentStack?: string }
  ) => void;
  onDefaultTransitionIndicator?: () => void | (() => void);
}

const container = document.getElementById('root');
if (!container) throw new Error('Root container not found');

const root: Root = createRoot(container, {
  onRecoverableError: (error, errorInfo) => {
    console.error('Recoverable error:', error);
  },
});

root.render(<App />);

Migration from React 17

Before (React 17)

import ReactDOM from 'react-dom';

ReactDOM.render(
  <App />,
  document.getElementById('root'),
  () => {
    console.log('Rendered!');
  }
);

// Unmounting
ReactDOM.unmountComponentAtNode(document.getElementById('root'));

After (React 18+)

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

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

// Move callbacks into component
function App() {
  useEffect(() => {
    console.log('Rendered!');
  }, []);
  
  return <div>...</div>;
}

root.render(<App />);

// Unmounting
root.unmount();

Common Pitfalls

Don’t call render in render

// Bad: Causes infinite loop
function App() {
  root.render(<App />); // Don't do this!
  return <div>App</div>;
}

// Good: Use state to trigger updates
function App() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

Don’t create multiple roots for same container

// Bad: Creating multiple roots for same element
const root1 = createRoot(container);
const root2 = createRoot(container); // Error!

// Good: Reuse the same root
const root = createRoot(container);
root.render(<App1 />);
root.render(<App2 />); // This is fine - updates the same root

Don’t forget to handle errors

// Bad: Errors crash the app silently
const root = createRoot(container);
root.render(<App />);

// Good: Handle errors gracefully
const root = createRoot(container, {
  onRecoverableError: (error) => {
    logToService(error);
  },
});

root.render(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>
);

Browser Compatibility

createRoot requires:
  • Modern browsers (Chrome 90+, Firefox 88+, Safari 14.1+, Edge 90+)
  • ES6 features: Map, Set, Promise
  • DOM features: Element, Document, DocumentFragment
For older browsers, include polyfills:
<script src="https://unpkg.com/core-js-bundle@3/minified.js"></script>

Performance Considerations

  1. Concurrent rendering - createRoot enables concurrent features like transitions and Suspense
  2. Automatic batching - Multiple state updates are batched automatically
  3. Interruptible rendering - React can pause and resume rendering work
  4. Smooth updates - Long-running updates don’t block user interactions