Expand your code review knowledge: free course access for the first 50 participants

7 min read

React JS: DOM and How the Reconciliation Process Works

What is Document Object Model and what is the difference between an actual and a virtual DOM? This article explores both concepts, and explains how the reconciliation process works.

The author of this article is EPAM Senior Software Engineer Anjani Tharayil.

Other articles by this author:

What is DOM?

DOM stands for Document Object Model, which is like an organized version of the HTML in a web page or app. It shows the whole user interface of a web app as a tree-like structure:

HTML and DOM representation

Below is a simplified diagram showing how rendering works in the browser:

How rendering works

When changes occur in the UI of a web application, due to user interactions or dynamic content updates, the browser’s rendering process needs to handle these changes to update the display accordingly. Here’s how the rendering process works when changes happen in the UI:

How rendering works when changes happen in UI

Rendering issues

The challenge lies in how regular UI updates impact an application’s performance, since they necessitate manipulation of the DOM and re-rendering processes.

AngularJS is a popular JavaScript framework that uses real DOM. One of its limitations is that it can be slow when rendering complex pages, because it has to update the entire DOM tree every time there is a change.

Virtual DOM

Virtual DOM serves as a duplicate of the real DOM. When modifications occur within the application, the virtual DOM is updated instead of the actual DOM.

React, a widely used framework, employs the concept of the virtual DOM.

When new elements are added to the UI, a virtual DOM, which is represented as a tree, is created. Each element is a node on this tree. If the state of any of the elements changes, a new virtual DOM tree is created. This tree is then compared or “diffed” (diffing algorithm) with the previous virtual DOM tree.

Once this is done, the virtual DOM calculates the best possible method to make these changes to the real DOM. This ensures that there are minimal operations on the real DOM (through the reconciliation process), reducing the performance cost of updating the real DOM.

Diffing algorithm: understanding efficient tree comparison

The process of detecting changes in a Document Object Model after manipulation is facilitated by a diffing algorithm. This algorithm plays a crucial role in optimizing the comparison of updated virtual DOMs with their snapshots before modification. By understanding how trees are compared, we can see how efficiently updates and rendering work in frameworks like React.

  • Comparing root elements

At the heart of the diffing algorithm is the comparison of root elements. This is where the algorithm assesses the changes between the updated virtual DOM and the virtual DOM snapshot before the update. Two scenarios arise depending on whether the root elements are of the same type or different types.

  • Diverse DOM elements

When the root elements differ in type, React dismantles the old tree and constructs a fresh one from scratch. The old DOM nodes are destroyed, triggering the componentWillUnmount() method in component instances. The new tree involves the insertion of new DOM nodes, leading to the execution of UNSAFE_componentWillMount() followed by componentDidMount(). Importantly, any information associated with the old tree is lost.

  • Identical DOM elements

In cases where the root elements share the same type, React preserves the underlying DOM node and updates only altered attributes:

<div className="before_changes" title="Changes" />

<div className="after_changes" title="Changes" />

By comparing these two elements, React exclusively modifies the className attribute of the underlying DOM node. Even for style updates, React updates only the changed properties:

<div style={{color: ’red’, fontWeight: ’bold’}} />

<div style={{color: ’green’, fontWeight: ’bold’}} />

React understands that only the color style should change, so it doesn’t bother with the fontWeight change.

  • Uniform component elements

When components undergo updates, their instances remain constant, ensuring the continuity of state across renders. React accomplishes this by updating the props of the underlying component instance to mirror the new element. Subsequently, it invokes UNSAFE_componentWillReceiveProps(), UNSAFE_componentWillUpdate(), and componentDidUpdate() on the existing instance. The render() method is then invoked, initiating recursive application of the diff algorithm on the preceding and current results.

  • Recursive assessment of children

By default, when traversing the children of a DOM node recursively, React simultaneously iterates through both child lists. It generates mutations whenever discrepancies surface. For instance, the following transformation showcases smooth tree conversion when an element is appended to the children:

Before:

<ul>

  <li>apple</li>

  <li>orange</li>

</ul>

After:

<ul>

  <li>apple</li>

  <li>orange</li>

  <li>banana</li>

</ul>

In this case, React aligns the <li>apple</li> and <li>orange</li> trees and subsequently integrates the <li>banana</li> tree.

Inserting an element at the start, however, can lead to performance degradation unless implemented carefully:

Before:

<ul>

  <li>Tata</li>

  <li>Toyota</li>

</ul>

After:

<ul>

  <li>Mahindra</li>

  <li>Tata</li>

  <li>Toyota</li>

</ul>

Here, React alters every child instead of recognizing the potential to preserve the <li>Tata</li> and <li>Toyota</li> subtrees. This inefficiency warrants attention.

  • Introducing keys

To address this concern, React introduced support for a key attribute. When children are assigned keys, React utilizes these keys to correlate children in the initial tree with those in the subsequent one. Consider the following enhancement of the previous example for optimized tree conversion:

Before:

<ul>

  <li key="2911">Tata</li>

  <li key="2912">Toyota</li>

</ul>

After:

<ul>

  <li key="2910">Mahindra</li>

  <li key="2911">Tata</li>

  <li key="2912">Toyota</li>

</ul>

React can tell that the element with the key ’2910’ is new, while the elements with the keys ’2911’ and ’2912’ have just been relocated.

Finding a key is often straightforward. The item to be displayed may already possess a distinct ID, thus serving as a suitable key:

<li key={item.id}>{item.name}</li>

If a unique ID is unavailable, introducing a new ID property or employing content-based hashing can suffice for key generation. It’s essential to note that the key must be unique among siblings, rather than globally distinct.

As a final alternative, you can employ the item’s index within the array as a key. While this works effectively for items that remain unaltered in their order, it becomes less efficient when reordering occurs. Reordering can also pose challenges related to component state, since indexes used as keys can lead to unintended mixing and updates in cases involving uncontrolled inputs.

Stack reconciler

The stack reconciler is a component of React that is responsible for updating the DOM when React components change. It works by recursively comparing the old and new versions of a component tree and then making the necessary changes to the DOM to reflect the new tree.

The stack reconciler is divided into two main phases: mounting and updating. During mounting, the stack reconciler creates a new DOM element for each React component and then attaches it to the DOM tree. During updating, the stack reconciler compares the old and new versions of the component tree and then makes the necessary changes to the DOM to reflect the new tree.

The stack reconciler is a recursive algorithm, which means that it calls itself to update the children of a component. This can lead to stack overflow errors if the component tree is too deep. To avoid this, the stack reconciler uses a technique called memoization to cache the results of previous reconciliations.

The stack reconciler is a simple but effective way to update the DOM when React components change. It is used in React 15 and earlier versions. In React 16, the stack reconciler was replaced by the virtual DOM reconciler, which is a more efficient way to update the DOM.

Some of the key concepts of the stack reconciler include:

  • Mounting: The process of creating a new DOM element for each React component and then attaching it to the DOM tree.
  • Updating: The process of comparing the old and new versions of the component tree and then making the necessary changes to the DOM to reflect the new tree.
  • Recursive algorithm: An algorithm that calls itself to solve a problem.
  • Memoization: A technique for caching the results of previous computations to avoid repeating them.
  • Virtual DOM: A lightweight representation of the DOM that can be used to efficiently update the real DOM.

Fiber reconciler

The fiber reconciler is a new reconciliation algorithm introduced in React 16. It is designed to improve the performance and scalability of React applications by introducing asynchronous rendering and a new scheduling algorithm.

The main difference between the fiber reconciler and the stack reconciler is that the fiber reconciler breaks down the reconciliation process into smaller chunks called fibers.

These fibers can be executed independently and prioritized based on their importance. This allows React to schedule updates more efficiently, resulting in better performance and scalability.

Some of the key features of the fiber reconciler:

  • Fibers are small units of work that represent a component in the React tree. Each fiber has a reference to its parent fiber, its children fibers, and its associated DOM node.
  • Worklets are asynchronous functions that can be scheduled to run during reconciliation. This allows React to perform resource-intensive operations such as animations and image decoding asynchronously, without blocking the main thread.
  • The fiber reconciler uses a new scheduling algorithm to prioritize updates based on their importance. This ensures that critical updates are rendered first, while less important updates can be deferred until later.
  • The fiber reconciler is a more complex algorithm than the stack reconciler, but it offers significant performance and scalability improvements. It is the default reconciliation algorithm in React 16 and later versions.

The key differences between the fiber reconciler and the stack reconciler at a glance:

Differences between the fiber reconciler and stack reconciler

Conclusion

The actual DOM represents the structure of a web page, while the virtual DOM is a lightweight abstraction that React uses for efficient updates. Reconciliation in React is the method through which the user interface (UI) is refreshed to mirror alterations in a component’s state. The reconciliation algorithm comprises the guidelines that React employs to ascertain the most resource-efficient approach for UI updates. React employs a virtual DOM to manage these UI updates. This makes React a highly performant library for building user interfaces.

React developer: sources of learning and future opportunities