Ibadehin Mojeed I'm an advocate of project-based learning. I also write technical content around web development.

What is the virtual DOM in React?

8 min read 2307

What Is A Virtual Dom In React

The virtual DOM is a fundamental React concept. You have probably heard of it if you have written React code in the last few years. However, you may not understand how it works and why React uses it.

This article will cover what the virtual DOM is, its benefits in React, and practical example code to help explain this concept.

In this article:

Concept review: What is the DOM?

To understand the virtual DOM and learn why React implements it, let us refresh our knowledge of the actual browser DOM.

Normally, whenever a web page is requested by a user, the browser receives an HTML document for that page from the server. The browser then constructs a logical tree-like structure from the HTML for the user to see the requested page in the client.

This tree-like structure is called the Document Object Model, also known as the DOM. It is a structural representation of the web document — in this case, an HTML document — as nodes and objects.

The DOM serves as an interface for the web document so that JavaScript and other scripting languages can access and programmatically interact with and manipulate the document’s content.

For instance, developers can use the DOM APIs to add or remove elements, modify their appearance, and perform user actions on the web elements.

How re-rendering impacts performance

DOM operations are lighter operations and very fast. However, when the app data changes and triggers an update, re-rendering can be expensive.

Let’s simulate a re-rendering page with the JavaScript code below:

const update = () => {
 const element = `
  <h3>JavaScript:</h3>
  <form>
   <input type="text"/>
  </form>
  <span>Time: ${new Date().toLocaleTimeString()}</span>
 `;

 document.getElementById("root1").innerHTML = element;
};

setInterval(update, 1000);

You can see the complete code on CodeSandbox.

The DOM tree representing the document looks like so:

Dom Tree Representing Example Javascript Document With Nodes For Header, Form Input, Time Span

The setInterval() callback in the code lets us trigger a simulated re-render of the UI after every second. As we can see in the GIF below, the document DOM elements are rebuilt and repainted on each update. The text input in the UI also loses its state due to this re-rendering:

Demonstration Of All Nodes Lighting Up After Full Page Re-Renders After Manipulating The Dom Time Text Input

As we can see above, the text field loses the input value when an update occurs in the UI. This calls for optimization.

Different JavaScript frameworks have different solutions and strategies to optimize re-rendering. React, however, implements the concept of virtual DOM.



Exploring the virtual DOM in React

As the name implies, virtual DOM is a “virtual” representation of the actual DOM. By virtual, we mean a much lighter replica of the actual DOM — in the form of objects — that can be saved in the browser memory.

A common misconception is that the virtual DOM is faster than or rivals the actual DOM. That is not correct! In fact, the virtual DOM’s operations support or add to those of the actual DOM. It provides a mechanism that lets the actual DOM computes minimal DOM operation when re-rendering the UI.

React deploys the concept of virtual DOM in the rendering process because it conforms with its declarative approach.

This approach lets us specify what state we want the UI to be in, after which React makes it happen. It abstracts manual DOM manipulations away from the developer, helping us write more predictable, unruffled code, so we can focus on creating components.

The virtual DOM allows developers to not worry about state transitions. Once we update the state, React ensures the DOM matches that state.

For instance, in our last example, on every re-render, React will ensure only the time gets updated in the actual DOM. This way, we won’t lose the value of the input field while the UI update happens.

The virtual DOM object

Let’s take a look at the following render code representing the React version of the previous JavaScript example:

// ...
const update = () => {
 const element = (
  <>
   <h3>React:</h3>
   <form>
    <input type="text" />
   </form>
   <span>Time: {new Date().toLocaleTimeString()}</span>
  </>
 );
 root.render(element);
};

For brevity, we have removed some of the code. You can see the complete code on CodeSandbox.

We can also write JSX code in plain React, like so:

const element = React.createElement(
 React.Fragment,
 null,
 React.createElement("h3", null, "React:"),
 React.createElement(
  "form",
  null,
  React.createElement("input", {
   type: "text"
  })
 ),
 React.createElement("span", null, "Time: ", new Date().toLocaleTimeString())
);

Note that you can get the React code equivalent of JSX code by pasting the JSX elements in a babel repl editor.

Now, if we log the React element in the console:

 const element = (
  <>
   <h3>React:</h3>
   <form>
    <input type="text" />
   </form>
   <span>Time: {new Date().toLocaleTimeString()}</span>
  </>
 );
 console.log(element)

We will have something like this:

Example Of A Virtual Dom Representing The React Version Of Previous Javascript Example After Logging React Element In The Console

The object, as seen above, is the virtual DOM. It represents the user interface.

How React implements the virtual DOM

To understand the virtual DOM strategy, we need to understand the two major phases that are involved: rendering and reconciliation.

When we render an application user interface, React creates a virtual DOM tree representing that UI and keeps it in memory. On the next update — in other words, when the data that renders the app changes — React will automatically create a new virtual DOM tree for the update.

To help explain this further, let’s visually represent the virtual DOM like so:

Two Rectangles Side By Side Labeled Initial Virtual Dom (Left) And Updated Virtual Dom (Right). Both Rectangles Show Div Class With Three Grouped Elements And Time Sub Element In Labeled Green Bubbles. Time Elements In Each Box Are Connected By Dotted Arrow Pointing To Updated Time Element On Right, Which Is Red Instead Of Green.

The image on the left is the initial render. As the time changes, React creates a new tree with the updated node, as seen on the right side.

Remember, virtual DOM is just an object representing the UI; nothing gets drawn on the screen.

After React creates the new virtual DOM tree, it compares it to the previous snapshot using a diffing algorithm to figure out what changes are necessary. This process is called reconciliation.

After the reconciliation process, React then uses a renderer library like ReactDOM, which takes the differ information to update the rendered app. This library ensures the actual DOM only receives and repaints the updated node or nodes:

Same Graphic As Shown Above, Now With Additional Rectangle To The Far Right Showing The Actual Dom. The Time Element Is Now Colored Blue And Surrounded In A Dotted Line Box With The Label Update And Repaint

As seen in the image above, only the node whose data changes gets repainted in the actual DOM. The GIF below further proves this statement:

Inspecting React Render Showing Only The One Updated Node Lighting Up As It Is Repainted On Re-Rendering

As we can also see, we are not losing the input value when a state change occurs in the UI.

In summary, on every render, React has a virtual DOM tree it compares with the previous version to determine what node content gets updated and ensure the updated node matches up with the actual DOM.

More on the diffing process

When React diffs two virtual DOM trees, it starts by comparing whether or not both snapshots have the same root element. If they have the same elements — in our case, the updated nodes are of the same span element type — React moves on and recurses on the attributes.

In both snapshots, no attribute is present or updated on the span element. React then repeats the procedure on the children. Upon seeing that the time text node has changed, React will only update the actual node in the real DOM.

On the other hand, if both snapshots have different element types – which is rare in most updates — React will destroy the old DOM nodes and build a new one.

For instance, going from span:

<span>Time: 04:36:35</span> 

To div:

<div>Time: 04:36:38</div>

In another example below, we render a simple React component that updates the component state after a button click:

import { useState } from "react";

const App = () => {
 const [open, setOpen] = useState(false);

 return (
  <div className="App">
   <button onClick={() => setOpen((prev) => !prev)}>toggle</button>
   <div className={open ? "open" : "close"}>
    I'm {open ? "opened" : "closed"}
   </div>
  </div>
 );
};
export default App;

Updating a component state re-renders the component. However, as seen below, on every re-render, React knows only to update the class name and the text that changed. This update will not hurt unaffected elements in the render:

Result Of Updating A Component State With Dom Showing Class Name And Changed Text Updating On Every Re-Render

See the code and demo on CodeSandbox.

How React diffs lists

When we modify a list of items, how React diffs the list depends on whether the items are added at the beginning or end of the list. Consider the following list:

<ul> 
  <li>item 3</li>
  <li>item 4</li>
  <li>item 5</li>
</ul>

On the next update, let’s append an item 6 at the end, like so:

<ul> 
  <li>item 3</li>
  <li>item 4</li>
  <li>item 5</li>
  <li>item 6</li>
</ul>

React compares the items from the top. It matches the first, second, and third items, and knows to only insert the last item. This computation is straightforward for React.

However, if we insert item 2 at the beginning, like so:

<ul> 
  <li>item 2</li>
  <li>item 3</li>
  <li>item 4</li>
  <li>item 5</li>
</ul>

Similarly, React compares from the top, and immediately realizes item 3 doesn’t match item 2 of the updated tree. It then sees the list as an entirely new one that needs to be rebuilt.

Instead of rebuilding the entire list, we want the DOM to compute minimal operations by only prepending item 2. React lets us add a key prop to uniquely identify the items, like so:

<ul> 
  <li key="3">item 3</li>
  <li key="4">item 4</li>
  <li key="5">item 5</li>
</ul>
 
<ul> 
  <li key="2">item 2</li>
  <li key="3">item 3</li>
  <li key="4">item 4</li>
  <li key="5">item 5</li>
  <li key="6">item 6</li>
</ul>

With the above implementation, React would know we have prepended item 2 and appended item 6. As a result, it would work to preserve the already available items and only add the new items in the DOM.

React is kind enough to alert us in the browser console if we omit the key prop whenever we map to render a list of items.

How the virtual DOM is different from the shadow DOM

Before we round up, here’s a question that often comes up: Is the shadow DOM the same as the virtual DOM? The short answer is that their behavior is different.

The shadow DOM is a tool for implementing web components. Take, for instance, the HTML input element range:

<input type="range" />

That gives us this result:
Blue And Gray Slider Bar With Setting Indicator In Center Of Bar

If we inspect the element using the browser‘s developer tools, we will only see a simple input element. However, internally, browsers encapsulate and hide other elements and styles that make up the input slider.

Using Chrome DevTools, we can enable the “Show user agent shadow DOM” option from “Settings” to see the shadow DOM like so:
Chrome Developer Tools In Dark Mode With Shadow Root User Agent Dropped Down To Show Shadow Dom
In the image above, the structured tree of elements from the #shadow-root inside the input element is called the shadow DOM tree. It provides a way to isolate components, including styles from the actual DOM.

That way, we are sure that a widget or component’s style — like the above input range — is preserved no matter where they are rendered. In other words, their behavior or appearance is never affected by other element’s style from the actual DOM.

Comparison chart: Real DOM vs. virtual DOM vs. shadow DOM

The table below summarizes the differences between the real DOM, virtual DOM, and shadow DOM:

 
Real DOM
Virtual DOM
Shadow DOM
Description
An interface for web documents; allow scripts to interact with the document
An in-memory replica of the actual DOM
A tool for implementing web components; an isolated DOM tree within an actual DOM for scoping purposes
Relevance to developers
Developers manually perform DOM operations to manipulate the DOM
Developers don’t have to worry about state transitions. It abstracts DOM manipulation away from the developer, thereby providing additional comfort
It lets developers create reusable web components without worrying about style conflicts from the hosting document. For example, it lets us create widgets like Twitter “follow” button to embed on web pages
Who uses them
Implemented in browsers
Used by libraries or frameworks like React.js, Vue.js, etc.
Implemented in browsers

Conclusion

React uses the virtual DOM as a strategy to compute minimal DOM operations when re-rendering the UI. It is not in rivalry with or faster than the DOM!

The virtual DOM provides a mechanism that abstracts manual DOM manipulations away from the developer, helping us write more predictable code. It does this by comparing two render trees to determine exactly what has changed and only update what is necessary on the actual DOM.

Like React, Vue and some other frameworks also employ this strategy. However, the Svelte framework proposes another approach to ensuring an application is optimized. It instead compiles all components into independent and tiny JavaScript modules, making the script very light and fast to run.

I hope you enjoyed reading this article. Share your thoughts in the comment section if you have questions or contributions.

Cut through the noise of traditional React error reporting with LogRocket

LogRocket is a React analytics solution that shields you from the hundreds of false-positive errors alerts to just a few truly important items. LogRocket tells you the most impactful bugs and UX issues actually impacting users in your React applications. LogRocket automatically aggregates client side errors, React error boundaries, Redux state, slow component load times, JS exceptions, frontend performance metrics, and user interactions. Then LogRocket uses machine learning to notify you of the most impactful problems affecting the most users and provides the context you need to fix it.

Focus on the React bugs that matter — .

Ibadehin Mojeed I'm an advocate of project-based learning. I also write technical content around web development.

3 Replies to “What is the virtual DOM in React?”

Leave a Reply