How React Works Under The Hood

How React Works Under The Hood

Kingsley Ubah's photo
Kingsley Ubah

Published on Oct 18, 2021

6 min read

How does React actually work beyond the syntactic sugar?

This question has dominated my thoughts for a very long time. My curiosity led me into carrying out a deep research on React, and my findings were quite interesting.

My quest to demystify React was one of the most eye-opening things I have ever undertaken.

So if you also interested in finding out how React really works under the hood then you’re at the right place.

In this article, I am going to share some of my interesting discoveries about the inner workings of React, in regards to the rendering process.

HTML, The DOM and Essence of React

We all know that a web page is structured using HTML. Hence, web pages are basically HTML Documents.

A HTML document can be modified through an API called the DOM using methods like querySelectorAll(), getElementsById() and so on.

After modification, the web browser has to re render the page to reflect those changes.

However, this process is very expensive for the web browser to undertake. So if you have a page which changes regularly (aka a dynamic web page), then directly manipulating the DOM would be very inefficient.

In fact, it is slower to repaint the DOM than it to create 10,000 objects with JavaScript.

This fact predicates the existence and usefulness of React as a library.

React provides you with a declarative way to create user interfaces. In other words, you don’t have to specify how exactly you want your web page to be rendered, or how the elements should be inserted. Instead you are more concerned with what elements to create and describing how they should look and behave.

React Elements are just Objects

You may or may not have known this, but React is just a tree of JavaScript objects.

For example, consider the following functional component:

const Title = () => {
    return (
     <div>
         <h1 id=”title”> Hello World </h1>
    </div>
  )
}

In React, a component is a class or function which describes the UI which you want to create on your webpage.

A React element is basically a description of the UI which you ultimately want to insert into the DOM. Hence, a React element is not a DOM element until the render() method of the ReactDOM library is called.

When a component is called from a Parent, React will call the render() method on that child element and return a React element, which is just a plain object with certain properties.

For example, calling the above functional component actually returns the following object.

{
  type: “div”,
  key: null,
  ref: null,
  “$$typeof”: Symbol(react.element),
  props: {
    children: {
      type: “h1”,
      key: null,
      ref: null,
      props: {
        id: “title”
        children: “Hello World!”
      },
    }
  }
}

The object contains certain properties.

  • type: This is a reference to the type of tag used. If an in-built DOM HTML element (such as main, div etc) is used, then the type points to the string representation of that DOM element. But if you import a custom React component using the typical import ... syntax, then you are referencing to a component element.

  • key: This property is used to identify an element amongst other children using unique values. This is usually when iterating over a list of children elements. Ref: a reference to an actual DOM node.

  • typeOf: The value of this property is always a symbol. Symbol is a JacaScipt data type introduced in ES6. The object takes in a value and returns a unique symbol. In the case of React, the Symbol object takes in a react.element. This is a protection feature against cross-scripting attacks. The feature is used a identify React elements to avoid scenarios where malicious values is passed to React.

  • props: This contains all of the elements children. If the component has multiple children, then the children property will be an array instead of an object. Each object has the same set of properties.

Now this object is what we call the virtual DOM.

The process of building up these objects is significantly cheaper than directly writing to the DOM. So instead of making direct modifications to the DOM, we can create a virtual DOM and make modifications on that object tree instead.

React creates a tree of elements whenever the render function is called.

Reconciliation

Reconciliation houses the diffing algorithm which determines what part of the tree we should replace.

In other words, this is how React reconciles the DOM tree with the React element tree when a change is made.

The diffing algorithm is the way which we can tell difference between the two trees and determine what parts of the tree we need to replace.

One important behaviour of React is how it reacts to type change on the top-level (root) element.

In such a case, React tears down the whole element tree and builds up a new tree.

For example if the div tag were to change to a span tag, then React will destroy the entire tree along with the DOM nodes. The following will also happen:

  • All old component instance (in div) will receive componentWillUnmount and equivalent useEffect hook

  • New component tree will be built up with span as root element.

  • React will start rerendering again

  • New nodes will inserted to the DOM

  • New components will receive componentWillMont and then ComponentDidMount and their useEffects hook will run

  • The old props and state (for div) will be discarded

If there is only a property change, then React will only update the changed property, and not tear down the entire tree.

So assuming we are moving from one product item to another product item as in the code

<ProductList>
     <Product id={5} /> // Product Five
</ProductList>
<ProductList>
     <Product id={6} /> // Product Six
</ProductList>

React will maintain the same component instances, pass in a new id props and then rerender so we navigate to a different page.

Children

Suppose we have a list of and we insert a new movie to the beginning of the list.

<ul>
<li> First item <li>
<li> Second item <li>
</ul>
<ul>
<li> New First item <li>
<li> First item <li>
<li> Second item <li>
</ul>

React has no way of knowing that a changes has occurred and where exactly the change happened.

As a result, React would tear down the tree and rebuild a new tree, but this is highly inefficient.

Instead, you should pass a unique value to the key property of each child.

React will recursively check for the unique values of the key properties and compare them. That way, it will know where on the list to insert the new item.

<ul>
<li key=”first”> First item <li>
<li key=”second”> Second item <li>
<ul>
<ul>
<li key=”new-first”> New First item <li>
<li key=”first”> First item <li>
<li key=“second”> Second item <li>
</ul>

Rendering to the DOM

import ReactDOM from 'react-dom'
import App from "./App.js";

ReactDOM.render(
     <App />,
     document.getElementById("root")
);

This process triggers the reconciliation process which builds up the DOM tree, the React element tree and the entire diffing process. Then React eventually inserts the React Component tree into the Browser DOM.

Wrapping Up

We have seen that React Elements are just regular objects. For all nested component, React generates a tree of objects which makes up the virtual DOM.

When then make updates on the virtual DOM through a process known as reconciliation.

P/S: Get weekly roundup of the best articles on web development by signing up to my newsletter

 
Share this