React 18: Legacy render vs New createRoot – What’s the Difference?
This article explains the differences between React 18’s legacy ReactDOM.render root API and the new ReactDOM.createRoot API, covering concepts of roots, hydration, rendering callbacks, and why both APIs are maintained, with code examples for each approach.
Overview
React 18 provides two root APIs, called Legacy Root API and New Root API.
Legacy Root API uses ReactDOM.render , works like React 17, and will be deprecated.
New Root API uses ReactDOM.createRoot , enabling all new React 18 features.
What is a root?
In React, a "root" is a pointer to the top‑level data structure that React uses to track the tree to render.
Legacy root is opaque to the user; it is attached to a DOM element.
<code>import * as ReactDOM from 'react-dom';
import App from 'App';
const container = document.getElementById('app');
// Initial render.
ReactDOM.render(<App tab="home" />, container);
// During an update, React would access
// the root of the DOM element.
ReactDOM.render(<App tab="profile" />, container);
</code>New Root API creates a root with
createRootand then calls
renderon it.
<code>import * as ReactDOM from 'react-dom';
import App from 'App';
const container = document.getElementById('app');
// Create a root.
const root = ReactDOM.createRoot(container);
// Initial render: Render an element to the root.
root.render(<App tab="home" />);
// During an update, there's no need to pass the container again.
root.render(<App tab="profile" />);
</code>What are the differences?
Reasons for the change include fixing ergonomics of passing the container on each render and removing the need for a separate
hydratemethod, replacing it with an option on the root.
Note: This change allows removing the hydrate method and replacing it with a root option; however, the latest versions deprecate hydrate: true on createRoot and introduce hydrateRoot instead.
What is hydration?
The
hydratefunction has moved to the
hydrateRootAPI.
Old version:
<code>import * as ReactDOM from 'react-dom';
import App from 'App';
const container = document.getElementById('app');
// Render with hydration.
ReactDOM.hydrate(<App tab="home" />, container);
</code>New version:
<code>import * as ReactDOM from 'react-dom';
import App from 'App';
const container = document.getElementById('app');
// Create *and* render a root with hydration.
const root = ReactDOM.hydrateRoot(container, <App tab="home" />);
// Unlike with createRoot, you don't need a separate root.render() call here
</code>Note that
hydrateRootaccepts JSX as its second argument because the initial client render must match the server tree.
What is a render callback?
Legacy Root API allowed passing a callback to
renderthat runs after rendering.
<code>import * as ReactDOM from 'react-dom';
import App from 'App';
const container = document.getElementById('app');
ReactDOM.render(<App tab="home" />, container, function() {
// Called after initial render or any update.
console.log('rendered');
});
</code>New Root API removed this callback; developers should use alternatives such as
requestIdleCallback,
setTimeout, or
refcallbacks.
Why support both APIs?
React 18 keeps the Legacy Root API for smooth upgrades and for experimentation, allowing developers to compare performance between the two.
KooFE Frontend Team
Follow the latest frontend updates
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.