React: How React Works Under The Hood

Ruchi Vora
7 min readNov 26, 2023
How React Works Under The Hood

React is a powerful javascript library that has become the darling of the tech world. It has reshaped the way we build user interfaces. After two years with Angular, I got a chance to explore React. Don’t get me wrong, Angular has its charms, but React has an undeniable allure.

In this blog, I aim to demystify React’s inner workings by explaining the core concepts like DOM, virtual DOM, reconciliation and diffing algorithms. By understanding these concepts, developers can harness the power of React to create efficient and responsive user interfaces. Additionally, gaining insights into React’s inner workings can also help troubleshoot any performance issues that may arise during development.

Document Object Model (DOM)

The DOM, or Document Object Model, is a representation of the HTML structure that allows JavaScript to interact with and manipulate the elements on a webpage. Each HTML element is a node in the DOM tree, with parent-child relationships based on their nesting in the HTML code.
Consider this example:

<html>
<head>
<title>This is title</title>
</head>
<body>
<h1>This is a header</h1>
<p> This is a paragraph</p>
</body>
</html>

The DOM tree for this HTML code will look like this.

DOM Tree

Web browsers utilize the DOM tree to represent and manipulate HTML documents, which enables easy identification and positioning of elements within the browser’s display.

However, modifying or making changes to DOM elements is a resource intensive and time-consuming process. When a DOM element is altered, the browser has to recalculate the element’s size and position, as well as repaint the screen.

This is where the virtual DOM comes into play. It functions as an efficient alternative for updating the DOM in response to changes, significantly reducing the time required. Therefore, React uses Virtual DOM.

Virtual DOM

The Virtual DOM is a JavaScript object representation of the actual DOM. Consider the HTML code snippet above, and below is the corresponding JavaScript object representation.

Javascript Object representation of DOM

React generates the javascript object of the HTML to be precise JSX (which is nothing but a syntactic sugar to the HTML) with the help of React.createElement.

Updating a virtual DOM is much faster and efficient than the actual DOM, as updating a virtual DOM does not require heavy web browser processing like painting and recalibrating the space. It involves updating the JavaScript object directly.

React further enhances and streamlines the process of updating the DOM with the help of reconciliation.

Reconciliation

Reconciliation is a key feature of React that efficiently updates the DOM by only making necessary changes. Although its a complex process, but here are the basic steps that it follows.

The reconciliation process involves the following key steps:

  1. React creates a virtual DOM, which is the lightweight copy of the real DOM in the object form.
  2. With every change in the component state or props, React creates a new virtual DOM.
  3. React uses a diffing algorithm which traverses the tree to compare/analyze the changes between the new virtual DOM and the previous one.
  4. The above step helps to determine which component needs to be re-rendered on the real DOM.
  5. Finally, the changes are made to actual/real DOM.

Diffing algorithm ensures that the minimum changes are to be made on the actual/real DOM.

Despite multiple state changes, due to the reconciliation process, the real DOM is updated only once, ensuring efficient rendering and a smooth user experience.

With the help of the above steps, I hope you now have a clear understanding of the reconciliation process and how it helps React apps run faster. Let’s go over the diffing algorithm in more depth.

Diffing Algorithm

Diffing algorithm is the heart of the reconciliation process which contributes to the efficiency and speed. The algorithm is very complex but it focuses on these key points.

1. Elements Of Different Types

When the root elements of the Virtual DOM trees have different types, React’s diffing algorithm discards the entire old DOM tree and constructs a new one from scratch. Consider this example:

Original DOM: 

<div>
<Counter/>
</div>

Updated DOM:

<span>
<Counter/>
</span>

In this example, the root node transforms from <div> to <span>. Since the root node's type has changed, the diffing algorithm removes the entire old DOM tree and rebuilds it from the ground up. This process involves unmounting and destroying the state of the <Counter> component and subsequently reconstructing it.

This behaviour is based on the assumption that elements of different types will produce distinct Virtual DOM structures. Therefore, when the root element’s type changes, the diffing algorithm assumes that the entire subtree has been replaced, necessitating a complete rebuild. This ensures that the DOM remains in sync with the application’s state, providing a consistent and reliable user experience.

2. DOM Elements Of The Same Type

When comparing two React DOM elements of the same type, React looks at the attributes, keeps the same underlying DOM node, and only updates the changed attributes. Consider this example:

Original DOM:
<div className="before" title="stuff" />

Updated DOM:
<div className="after" title="stuff" />

Given that the underlying <div> element remains the same, React effectively updates the className attribute on the existing DOM element, preserving its underlying structure.

3. Component Elements Of The Same Type

When comparing component elements of the same type, the diffing algorithm takes advantage of the fact that the underlying DOM nodes for components persist across renders.
This means that React doesn’t need to recreate the entire DOM subtree for the component, but instead, it can focus on updating the properties and state of the existing component instance. I know this is a bit confusing but Consider this example:

<Counter count={0}>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<span>{count}</span>
</Counter>

Consider you have a React component called Counter that displays a counter value and provides buttons to increment and decrement the count.

Now, suppose the user clicks the increment button, updating the counter value to 1. React’s diffing algorithm will compare the old and new Virtual DOM trees. Since the root element (Counter) is of the same type, the diffing algorithm will focus on updating the properties, and state of the existing component instance and will not create the new DOM element from scratch.

4. Recursing On Children

By default, when recursing on the children of a DOM node, React just iterates over both lists of children at the same time and generates a mutation whenever there’s a difference. Consider this example

Original DOM:

<ul>
<li>first</li>
<li>second</li>
</ul>

Updated DOM:

<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>

In this example, the original DOM contains two elements within the <ul> list. However, in the updated DOM, a new element, <li>third</li>, is added to the end of the list. The diffing algorithm straightforwardly compares the tree structures and identifies the new element, seamlessly adding it to the DOM. This process is clear and intuitive.

But consider this example and observe it carefully.

Original DOM:

<ul>
<li>first</li>
<li>second</li>
</ul>

Updated DOM:

<ul>
<li>zero</li>
<li>first</li>
<li>second</li>
</ul>

In this example, the original DOM contains two elements in the <ul> list. However, in the updated DOM, a new element is added at the first position in the list. As a result, the diffing algorithm compares the first element in the list, i.e., <li>first</li>, and <li>zero</li>.

Because they are different, the algorithm reconstructs the element. Similarly, when comparing the second element, <li>second</li>, and <li>first</li>, it again reconstructs the element. This approach results in the entire list being reconstructed, which is not an efficient process.

To overcome this issue, the keys are introduced in React.

Original DOM:

<ul>
<li key="first">first</li>
<li key="second">second</li>
</ul>

Updated DOM:

<ul>
<li key="zero">zero</li>
<li key="first">first</li>
<li key="second">second</li>
</ul>

The Diffing algorithm can now intelligently compare keys and selectively update only those elements whose keys have been modified. This results in a more efficient update of the DOM, optimizing the rendering process.

That’s the reason we see a warning to provide keys to list items that React throws.

Types of Reconciliation Algorithms

In this blog, I will just cover the broad overview of this:

1. Stack reconciliation algorithm:

Prior to React 16, the diffing algorithm was responsible for identifying changes between the Virtual DOM and the actual DOM, and the Stack reconciliation algorithm was tasked with applying those changes to the DOM. This means each change that needs to be performed was pushed onto the stack and then executed synchronously.

The various challenges with stack based approach include:
1. The Stack reconciliation algorithm could only process updates one at a time, leading to delays when multiple state changes occur simultaneously.

2. Furthermore, when transitioning from state A to C in a sequence like A → B → C, the ideal scenario would be to directly display the eventual state C on the screen. However, in a stack-based algorithm, the intermediate state B is visible between A and C. This not only introduces inefficiencies but the transition is visible on the screen.

3. Also, while these updates are being made the UI becomes unresponsive.

4. It also affected the performance in the case of animation.

2. Fiber reconciliation algorithm:

To address the limitations of the stack based reconciliation, from React 16 onwards the Fiber reconciliation algorithm, a more performant and flexible approach to updating the DOM was introduced.

The various improvement over the Stack based approach includes:
1. Fiber can handle multiple updates simultaneously, allowing the UI to remain responsive even when multiple state changes occur.

2. Fiber employs lane-based scheduling to prioritize updates based on their urgency, ensuring that critical updates are processed promptly while non-essential updates can be done afterwards.

3. Fiber breaks down the updates to be done into smaller chunks. This allows the algorithm to interleave with other tasks, such as handling user interactions or rendering animations, ensuring that the UI remains responsive even when updates are being applied.

This is the overview of how React works under the hood. I hope I could help you in understanding this topic. Feel free to comment in case you have any doubts or suggestions.

--

--