DEV Community

Cover image for ReactDOM.createRoot 🆚 ReactDOM.render
Nick
Nick

Posted on

ReactDOM.createRoot 🆚 ReactDOM.render

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);
Enter fullscreen mode Exit fullscreen mode

👉 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) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

👉 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);
}
Enter fullscreen mode Exit fullscreen mode

👉 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!" />);
Enter fullscreen mode Exit fullscreen mode

👉 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) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

👉 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);
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
jaewangl profile image
JaeWang Lee • Edited

So useful!

Collapse
 
fromaline profile image
Nick

Thanks 🙏🏻
I appreciate this kind of feedback a lot!