← Back to Chapters

createRoot() vs render()

⚛️ createRoot() vs render()

? Quick Overview

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.

? Key Concepts

  • Legacy synchronous rendering vs concurrent rendering
  • Automatic batching in React 18
  • Modern root-based rendering API
  • Better support for Suspense and transitions

? Old Method: ReactDOM.render()

This method was used in React 17 and below. It renders synchronously and does not support concurrent features.

? View Code Example
// 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"));
  • Synchronous rendering only
  • No concurrent features
  • Deprecated in React 18

? New Method: ReactDOM.createRoot()

React 18 applications should use createRoot() from react-dom/client. This enables concurrent rendering.

? View Code Example
// 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 />);
  • Supports concurrent rendering
  • Enables automatic batching
  • Works with Suspense and transitions

? Migration Steps

? View Code Example
// Migration from render() to createRoot()
// ❌ Old API
ReactDOM.render(<App />, document.getElementById("root"));

// ✅ New API
const root = createRoot(document.getElementById("root"));
root.render(<App />);
  1. Import from react-dom/client
  2. Create a root instance
  3. Call root.render()

? Interactive Simulation: Batching

This demo visualizes the performance difference. We will trigger 3 state updates in a row. Watch how many times the component "re-renders" (flashes).

Legacy render()

React 17 Behavior

0
Ready...

Modern createRoot()

React 18 Automatic Batching

0
Ready...

⚖️ Comparison Summary

  • render() → legacy, synchronous, deprecated
  • createRoot() → modern, concurrent, optimized

? Concurrent Feature Example

? View Code Example
// 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().

? Tips & Best Practices

  • Always use createRoot() in React 18+
  • Use hydrateRoot() for SSR
  • Concurrent hooks require the new root API
  • Enable StrictMode during development

? Try It Yourself

  1. Migrate a React 17 app to React 18
  2. Replace render() with createRoot()
  3. Add useTransition() for performance testing
  4. Compare render performance before and after