React | Working Principle Under the Hood

7 mintutorial

As you know, React is the most popular UI library for web and native users. Here, I want to talk about the working principle of the react and its basics.

What Does React Do: Component, React Element and Component Instance

React is a declarative way of building UI instead of using vanilla Javascript imperatively. It helps us to create the entire app tree in the shape of an object (Virtual DOM) that describes the HTML. To do that, React uses JSX a syntax extension for JavaScript that lets you write HTML-like markup inside a JS file- It looks like HTML, but under the hood, it is transformed into plain javascript objects. A Component is just a plain javascript function returning JSX

const MyComponent = () => {
  return (
    <div>
      <h1> This is an example </h1>
    </div>
  );
};
JSX Example

Thanks to babel.js or other js compilers, the React Component’s returned JSX is converted to React.createElement function calls.

Here is the compiled version of the above React Component

const MyComponent = () => {
  return React.createElement(
    "div",
    null,
    React.createElement("h1", null, " This is an example "),
  );
};
Compiled JSX

when that function is called as MyComponent(), the React.createElement function returns that output object below. That object is called a React Element. It describes the related HTML element as the shape of an object.

{
  $$typeof:Symbol(react.element)
  key:null
  props:{
    children:{
      $$typeof:Symbol(react.element)
      key:null
      props:{
        children:"This is an example"
      }
      ref:null
      type:"h1"
    }
  ref:null
  type:"div"
}
}
function returns as react element

Normally, React doesn’t call your component like that. You place your components with tags like that <MyComponent/>, and when react encounters that component tag during rendering, it calls that component behind the scenes and creates a Component Instance for it. Each instance has a lifecycle and internal state also, which we will talk about soon.

There is a tiny difference between calling the component <MyComponent /> and MyComponent(). That difference is the type of property. If you call your component just like a function with parenthesis, you will get an object like below. However, if you put your component with tags, react calls that component when it encounters it. Here is the output of <MyComponent/>

{
  $$typeof:Symbol(react.element)
  key:null
  props:{}
  ref:null
  type:()=>{...} // name of the function is MyComponent
}
Output of <MyComponent/>

As you can see, now the type is a function that references to MyComponent, and when that type function gets called, it returns the original react element that describes the HTML element. You can check with yourself

const myComp = <MyComponent />;
console.log(myComp.type());

Not only does React call our component for us, but it also manages the component instances. React iterates over all component returns and it creates the entire object html tree.

At that point, we talked about how react calls our component and what the component looks like under the hood. If you wonder when react starts that process, stay with me :)

The Magic Wand of Rendering: ReactDOM

We have talked about react and how it calls our components. However, to start that process, we need a spark. Yes, that spark is the render method of ReactDOM. It helps us to talk with the browser’s actual DOM and updates our changes on the screen.

Lets look what is happening during rendering ReactDOM.render(<App/>)

  1. React calls the App Component under the hood

  2. Iterates over its returns ( as you remember, when we call a component, an object that describes the html element is returned)

  3. Calls the functions that are returned from it (type property of the component call is a function that references to that component)

  4. Extract their returns, and so on until it creates the entire tree (Virtual DOM tree). If we dive deep into that process, there is an important point which is the Reconciliation

Actually, they work much more complex but I’ll try to explain broadly.

Reconciliation

When we call ReactDOM.render() method, the reconciliation process starts concurrently. The main duty of that process is creating a tree of elements thanks to React.createElement() calls, and updates the browser DOM by syncing with the virtual DOM where react creates a shape of a javascript object that describes the HTML. This process is very fast since react elements are just plain JavaScript objects. This tree of elements is kept in memory as a Virtual DOM. Also, during the initial render, react inserts the full tree.

Up to now, everything is okay but what if my app has an interactivity that causes the UI change such as a counter button when the user clicks, it updates the counter value? How browser will understand that it needs to update its DOM? Here comes another important point which is Diffing Algorithm

Diffing Algorithm

When there is a change via setState or other re-render trigger functions, react creates a new Virtual DOM (component instances shape of a javascript objects), and compares it with the previous one by traversing down the tree from the current component instance.

It enhances the performance by updating only the required components/ nodes.

As you remember, the component instance looks like an object

{
  $$typeof:Symbol(react.element)
  key:null
  props:{
    children:{
      $$typeof:Symbol(react.element)
      key:null
      props:{
        children:"This is an example"
      }
      ref:null
      type:"h1"
    }
  ref:null
  type:"div"
  }
}

The diffing algorithm will check the reference of that object first.

— If the object reference is the same, then react skips that component instance, and no flag process happens. For example, when setState is called in a wrapper component, and this component has children. In that case, when setState is called react will create a new virtual dom, and the diffing algorithm will check the references. Since it takes a child, in a new virtual dom, the reference will be the same as it is outside of the component. Therefore, it will not call the children component. ( You can think of that as an optimization like memoization )

const MyComponentOutside = () => {
  console.log("I am rendered");
  return (
    <div>
      <h1>This is an example</h1>
    </div>
  );
};
 
function App() {
  return (
    <div>
      <MyComponentWrapper>
        <MyComponentOutside />
      </MyComponentWrapper>
    </div>
  );
}
const MyComponentWrapper = ({
 children,
}: {
 children: ReactNode;
}) => {
 const [counter, setCounter] = useState(0);
 return (
  <div>
   <span>{counter}</span>
   <button onClick={() => setCounter((prev) => prev + 1)}>
    +
   </button>
   <div>{children}</div>
  </div>
 );
};

— If the object is different which means the different reference, the algorithm goes one step further and checks the type.

  • If the type is different, react will flag that component instance and delete it (its internal state and all lifecycles are removed). That is also called unmount.

  • If the type is the same, react will flag that component instance as needing re-render, and update its props only

React Rendering Reconciliation

Examples of Diffing Algorithm

— Elements of Different Types

<div>
  <Counter />
</div>
 
<span>
  <Counter />
</span>

— Elements of the Same Type

<div className="before" title="stuff" />
<div className="after" title="stuff" />
<div style={{ color: "red", fontWeight: "bold" }} />
<div style={{ color: "green", fontWeight: "bold" }} />
<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>
 
<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

Conclusion

React is like the heart of the UI in modern web/mobile development. It helps us to create the entire app tree in the shape of an object. Therefore, you need a renderer to talk with the browser or Android/IOS. On the web, you use ReactDOM to render React’s tree however if you use React Native, you can render your objects for an Android/IOS environment.

Whether you’re rendering to the DOM with ReactDOM or to a native platform with React Native, the core concepts of virtual DOM diffing and reconciliation remain consistent. This consistency allows React developers to write their components using the same mental model and patterns regardless of the target platform, which is one of the strengths of the React ecosystem.


Refs