A journey through ReactDOM.render
Tags: react, javascript, reactdom
Published: 2019-03-26 23:32:15

---
title: A journey through ReactDOM.render
published: true
description: What happens when you call ReactDOM.render()?
tags: reactjs, javascript, reactDOM
---

**This was originally [posted](https://medium.com/@CarlMungazi/a-journey-through-reactdom-render-e8fc5edc11fd) on Medium**

ReactDOM is an object which exposes a number of top-level APIs. According to the docs, it provides *‘DOM-specific methods that can be used at the top level of your app and as an escape hatch to get outside of the React model if you need to’.* One of those methods is `render()`.

## Some background notes

At the core of this re-write is a data structure called a **fiber**, which is an object that is mutable and holds component state and DOM. It can also be thought of as a virtual stack frame. Fiber’s architecture is split into two phases: **reconciliation/render** and **commit**. Over the course of this article we will touch upon some of its aspects but more extensive explanations can be found here:

- [An in-depth overview of reconciliation in React](https://medium.com/react-in-depth/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react-e1c04700ef6e)
- [How and why React uses a linked list to update components](https://medium.com/react-in-depth/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-67f1014d0eb7)
- [Notes on React Fiber’s architecture](https://github.com/acdlite/react-fiber-architecture)
- [Mozilla staff engineer Lin Clark’s talk on React Fiber](https://www.youtube.com/watch?v=ZCuYPiUIONs)
- [React core member Sebastian Markbage’s explanation on the principles behind React Fiber](https://github.com/facebook/react/issues/7942#issue-182373497)

## The journey begins

`ReactDOM.render()` actually wraps another function and invokes it with two additional arguments, so its declaration is simple. Below is the wrapped function:

```js
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
  ...
  let root = container._reactRootContainer;
  if (!root) {
    // Initial mount
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function () {
        const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
        originalCallback.call(instance);
      };
    }
    // Initial mount should not be batched.
    DOMRenderer.unbatchedUpdates(() => {
      if (parentComponent != null) {
        root.legacy_renderSubtreeIntoContainer(
          parentComponent,
          children,
          callback,
        );
      } else {
        root.render(children, callback);
      }
    });
  } else {
    ...
  }
  return DOMRenderer.getPublicRootInstance(root._internalRoot);
}
```

The first thing React does is create the `fiber tree` for our application. Without this, it cannot handle any user updates or events. The tree is created by calling `legacyCreateRootFromDOMContainer`, which returns the following object:

```js
{
  containerInfo: div#root
  context: null
  current: FiberNode {tag: 3, key: null, /*…*/}
  didError: false
  earliestPendingTime: 0
  earliestSuspendedTime: 0
  expirationTime: 0
  finishedWork: null
  firstBatch: null
  hydrate: false
  interactionThreadID: 1
  latestPendingTime: 0
  latestPingedTime: 0
  latestSuspendedTime: 0
  memoizedInteractions: Set(0) {}
  nextExpirationTimeToWorkOn: 0
  nextScheduledRoot: null
  pendingChildren: null
  pendingCommitExpirationTime: 0
  pendingContext: null
  pendingInteractionMap: Map(0) {}
  timeoutHandle: -1
}
```

This is called a **fiber root object**. Every React app has one or more DOM elements that act as containers and for each of these containers, this object is created. It is on this object we find a reference to our fiber tree via the `current` property, and its value is:

```js
{
  actualDuration: 0
  actualStartTime: -1
  alternate: null
  child: null
  childExpirationTime: 0
  effectTag: 0
  elementType: null
  expirationTime: 0
  firstContextDependency: null
  firstEffect: null
  index: 0
  key: null
  lastEffect: null
  memoizedProps: null
  memoizedState: null
  mode: 4
  nextEffect: null
  pendingProps: null
  ref: null
  return: null
  selfBaseDuration: 0
  sibling: null
  stateNode: {current: FiberNode, containerInfo: div#root, /*…*/}
  tag: 3
  treeBaseDuration: 0
  type: null
  updateQueue: null
}
```

This is a **fiber node** and it is found at the root of every React fiber tree ([this](https://github.com/facebook/react/blob/v16.7.0/packages/react-reconciler/src/ReactFiber.js#L85) is the function which creates this object). This node is actually a special type of fiber node called a `HostRoot` node and it acts as a parent for the uppermost component in our application. We know it is a `HostRoot` node because the value of its `tag` property is **3** (you can find the full list of fiber node types [here](https://github.com/facebook/react/blob/cbbc2b6c4d0d8519145560bd8183ecde55168b12/packages/shared/ReactWorkTags.js#L34)). Notice how at this stage, a lot of the properties, especially `child`, are `null`. We will come back to this later.

After creating the tree, React checks if we provided a callback as the third argument of the render call. If so, it grabs a reference to the root component instance of our application (in our case, it is the `<h1>` element) and then makes sure our callback will be called on this instance later on.

## Into the thick of it

All the stuff that has happened prior to this point is preparation for the work that will render our application on screen. So far, we have a tree but it does not resemble our UI. This problem is solved with the following code:

```js
// Initial mount should not be batched.
unbatchedUpdates(function () {
  if (parentComponent != null) {
    root.legacy_renderSubtreeIntoContainer(parentComponent, children, callback);
  } else {
    root.render(children, callback);
  }
});
```

`Root` is an object with only one property (the property `_internalRoot` which holds a reference to the root fiber object). It was created by calling `new` on the [ReactRoot](https://github.com/facebook/react/blob/v16.7.0/packages/react-dom/src/client/ReactDOM.js#L364) function, so if you look up its internal `[[Prototype]]` chain you will find the following method:

```js
ReactRoot.prototype.render = function (children, callback) {
  var root = this._internalRoot;
  var work = new ReactWork();
  callback = callback === undefined ? null : callback;
  {
    warnOnInvalidCallback(callback, 'render');
  }
  if (callback !== null) {
    work.then(callback);
  }
  updateContainer(children, root, null, work._onCommit);
  return work;
}
```

Remember our `HostRoot` fiber node earlier with all the `null` values? In the function above, we can access it via `root.current`. After `updateContainer()` has finished its work, this is what it looks like:

```js
{
  actualDuration: 6.800000002840534
  actualStartTime: 95592.79999999853
  alternate: FiberNode {tag: 3, key: null, /*…*/}
  child: FiberNode {tag: 5, key: null, /*…*/}
  childExpirationTime: 0
  effectTag: 32
  elementType: null
  expirationTime: 0
  firstContextDependency: null
  firstEffect: FiberNode {tag: 5, key: null, /*…*/}
  index: 0
  key: null
  lastEffect: FiberNode {tag: 5, key: null, /*…*/}
  memoizedProps: null
  memoizedState: {element: {/*…*/}}
  mode: 4
  nextEffect: null
  pendingProps: null
  ref: null
  return: null
  selfBaseDuration: 2.5999999925261363
  sibling: null
  stateNode: {current: FiberNode, containerInfo: div#root, /*…*/}
  tag: 3
  treeBaseDuration: 3.1999999919207767
  type: null
  updateQueue: {baseState: {/*…*/}, firstUpdate: null, /*…*/}
}
```

And the value in the child property is now a fiber node for the `<h1>` element:

```js
{
  actualDuration: 4.100000005564652
  actualStartTime: 95595.49999999581
  alternate: null
  child: null
  childExpirationTime: 0
  effectTag: 0
  elementType: "h1"
  expirationTime: 0
  firstContextDependency: null
  firstEffect: null
  index: 0
  key: null
  lastEffect: null
  memoizedProps: {children: "Hello, world!"}
  memoizedState: null
  mode: 4
  nextEffect: FiberNode {tag: 3, key: null, /*…*/}
  pendingProps: {children: "Hello, world!"}
  ref: null
  return: FiberNode {tag: 3, key: null, /*…*/}
  selfBaseDuration: 0.5999999993946403
  sibling: null
  stateNode: h1
  tag: 5
  treeBaseDuration: 0.5999999993946403
  type: "h1"
  updateQueue: null
}
```

As you can see, the fiber tree now reflects the UI we want rendered. How did that happen? Below is a summary of the steps which took place in order to get us to this stage:

### Schedule the updates

React has an internal [scheduler](scheduler) which it uses for handling [co-operative scheduling](https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API) in the browser environment. This package includes a [polyfill](https://github.com/facebook/react/blob/v16.7.0/packages/scheduler/src/Scheduler.js#L415) for [`window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback), which is at the heart of how React reconciles UI updates. During the process each fiber node is given an **expiration time**. This is a value which represents a time in the future the **work** (the name given to all the activities which happen during reconciliation) being done should be completed.

This calculation involves checking if there is any pending work with a higher priority as well as the nature of the work currently going on. In our case, it is a synchronous update but in other scenarios it could be interactive or asynchronous. Examples of low priority updates include network responses or clicking a button to change colours. High priority updates include user input and animations.

### Create an update queue

In our example, we only have one fiber tree and since it has no update queue, one has to be created and assigned to the `updateQueue` property. This queue is created with the [`createUpdateQueue`](https://github.com/facebook/react/blob/v16.7.0/packages/react-reconciler/src/ReactUpdateQueue.js#L152) function and it takes the fiber node's **memoizedState** as its argument. Memoized state refers to the state used to create the node's output. The queue is a linked list of prioritised updates and like fiber trees, it also comes in pairs. The **current** queue represents the visible state of the UI.

### Create the work-in-progress tree and do work on it

When the work is actually being performed, React checks its internal stack to determine whether it is starting with a fresh stack or resuming previously yielded work. Soon after this check it uses a technique known as [double buffering](https://en.wikipedia.org/wiki/Multiple_buffering#Double_buffering_in_computer_graphics) to [create](https://github.com/facebook/react/blob/v16.7.0/packages/react-reconciler/src/ReactFiber.js#L346) the **workInProgress** tree. React works with two fiber trees — one named **current** which holds the current state of the UI and another named **workInProgress** which reflects the future state. Every node in the **current** tree has a corresponding node in the **workInProgress** tree created from data during render.

Once the **workInProgress** is rendered on the screen, it becomes the new **current** tree. The **workInProgress** tree also has an update queue but it can be mutated and processed asynchronously before being rendered.

### DOM element creation

The **fiber node** for the `<h1>` element is created during the work on the **workInProgress** tree. A few calls later, [this](https://github.com/facebook/react/blob/c954efa70f44a44be9c33c60c57f87bea6f40a10/packages/react-dom/src/client/ReactDOMComponent.js#L374) function uses the DOM API to create our `<h1>` element, which so far has been a React element object. When the application has rendered, you can access the element by typing `document.querySelector('h1')`. If you check its properties, you will find one beginning with `__reactInternalInstance$`. This property holds a reference to the element's fiber node. React also [assigns](https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/client/setTextContent.js#L21) the element's text content and then later [appends](https://github.com/facebook/react/blob/c954efa70f44a44be9c33c60c57f87bea6f40a10/packages/react-dom/src/client/ReactDOMHostConfig.js#L361) it to our root DOM element. By the time its appended, React has already entered its **commit** phase.

![Accessing React Element via the DOM](https://thepracticaldev.s3.amazonaws.com/i/6g8g07i0i0usyv8ucc2q.png)

## And that is all there is to it

In our example we have rendered the most basic of UIs but React has done a lot of work to prepare and display it. Like most frameworks created for building complex applications, it does this work to prepare for all the eventualities associated with such a task. And with the example application being static, we have not touched on any of the code involved in state updates, lifecycle hooks and the rest of it.

*As this is my first time doing a deep dive on React, I am sure I have missed things out or incorrectly explained others, so please, leave your feedback below!*