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!