What is ReactDOM.createRoot and how is it different from ReactDOM.render?
React 18 introduces a new root API, so let's figure it out
👉 Current API
We have to pass the container
to render function every time we want to explicitly render.
const container = document.querySelector('#root');
// Initial render. Container is explicitly accessed.
ReactDOM.render(<App text="Hello" />, container);
// Subsequent renders. Container is explicitly accessed.
ReactDOM.render(<App text="Hello world!" />, container);
👉 What does ReactDOM.render
take?
render
function takes three arguments:
- React element to be rendered
- DOM element to render in
- function to be executed after render happens
And returns the same container
, but with the rendered component.
/**
* @param element - React element to be rendered
* @param container - DOM element to render in
* @param callback - function to be executed after render happens
* @return container - container with renderned component
*/
function render(element, container, callback) {
// ...
}
👉 How does ReactDOM.render
work under the hood?
ReactDOM.render
does a couple of validation checks:
- whether the container is a suitable node
- whether the container wasn't previously passed to
createRoot
Then it passes all received arguments to legacyRenderSubtreeIntoContainer
.
// simplified structure
function render(element, container, callback) {
if (isValidContainer(element)) {
throw Error('Target container is not a DOM element.');
}
if (isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined) {
// don't throw an error, but logs it into console
error('container was previously passed to ReactDOM.createRoot().');
}
return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}
👉 New API
It fixes the issue of passing the container every time we want to explicitly render.
// First, we create a root
const root = ReactDOM.createRoot(document.querySelector('#root'));
// Initial render. Container is implicitly accessed.
root.render(<App name="Hello" />);
// Subsequent renders. Container is implicitly accessed.
root.render(<App name="Hello world!" />);
👉 What does ReactDOM.createRoot
take?
createRoot
function takes only one mandatory argument - DOM element to render in.
And returns RootType
, which has render and unmount methods.
P.S. Also createRoot
takes the second RootOptions
argument, but we'll examine it in the future.
/**
* @param container - DOM element to render in
* @param options - options, related to hydration
* @return RootType - instance of root
*/
function createRoot(container, options) {
// ...
}
👉 How does ReactDOM.createRoot
work under the hood?
On top of the render function, createRoot
:
- checks whether the container isn't a body element
- provide a bit more detailed warnings
Then createRoot
instance a new ReactDOMRoot
object and returns it. No legacyRenderSubtreeIntoContainer
at all!
// simplified structure
function createRoot(container, options) {
if (isValidContainer(element)) {
throw Error('Target container is not a DOM element.');
}
if (container.nodeType === 1 && container.tagName.toUpperCase() === 'BODY') {
console.error('Creating roots directly with document.body is discouraged');
}
if (isContainerMarkedAsRoot(container) {
if (container._reactRootContainer) {
console.error('container was previously passed to ReactDOM.render().')
} else {
console.error('container has already been passed to createRoot() before.');
}
}
return new ReactDOMRoot(container, options);
}
Now you know the underline difference between old render and new createRoot
functions!
Let me know, if you want a further comparison of legacyRenderSubtreeIntoContainer
and new ReactDOMRoot
.
P.S. Follow me on Twitter for more content like this!
Top comments (2)
So useful!
Thanks 🙏🏻
I appreciate this kind of feedback a lot!