React Reconciliation

Fatemeh Paghar - Dec 15 '22 - - Dev Community

In React projects, the app starts to render in the following code, which is located in the index.js file:

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

An instance of the App component is created by the ReactDOM.render method and will lead to calling the render method of the relevant component. There may be several components defined in the mentioned method.

As a result, for each component, the corresponding render method will be called. Finally, a hierarchical structure of HTML elements is formed and will be inserted into an element with the root ID by the second argument of the above method.

For example, consider the following components, each in their file:

import React, {useEffect, useState} from "react";
import Button from "./Button";

const Counter = () => {
   const[counter, setCounter] = useState(0);

   useEffect(()=>{
    console.log("Render Counter Component")
   })

   const btnClick = () => {
       setCounter(counter+1)
   }   

    return(    
        <div>
            <span>{counter}</span>
            <Button onClick={btnClick}></Button>        
        </div>
    )

}

export default Counter
Enter fullscreen mode Exit fullscreen mode
import React, { useEffect } from "react";

const Button = ({onClick}) => {

   useEffect(()=>{
    console.log("Render Button Component"); 
   }) 

   return(
        <div>
            <button onClick={()=>onClick()}>Click me</button>
        </div>
   )
}

export default Button
Enter fullscreen mode Exit fullscreen mode

Finally, inside the App.js component, we will have a similar structure:

function App() {

  useEffect(()=>{
    console.log("Render App Component")
  })

  return (
    <div className="App">
      <h1>Reconciliation Process</h1>
      <Counter/>
    </div>
  );

}
Enter fullscreen mode Exit fullscreen mode

In the render method of each of the above components, a console.log is written. By running the app, the following output can be seen in the browser console:

Render App Component
Render List Component
Render ActionButton Component
Enter fullscreen mode Exit fullscreen mode

The reason is that when the application is running, React will request all components to call their render method. When the HTML content is displayed on the page, the application will be in the reconciled state, at this point, the displayed output is consistent with the components' state.

React will wait for a change, which in most applications will be done by the user, which finally leads to calling the setCounter method. The setCounter method updates a component's state data. But this makes the component stale. It means that the HTML content displayed to the user will be out of date and several state data may be changed with just one event. This will cause the render method to be called for all changed components. But since updating the DOM is a costly operation, as a result, React compares the previous content (cached as Virtual DOM) with the new content to have the least amount of DOM updating. This process is called reconciliation.

To better understand this process, we will add an Id to the div element inside the List component:

 <div id="wrapper">
     <span>{counter}</span>
     <Button onClick={btnClick}></Button>        
 </div>
Enter fullscreen mode Exit fullscreen mode

Now, while the project is running, run the following code in the browser console:

document.getElementById("wrapper").classList.add("wrapper")

As you can see, the below code added a message class to the div element:

.wrapper{
  border: 1px solid green;
  padding: 2rem;
}
Enter fullscreen mode Exit fullscreen mode

Now, when we click on the Click me button, the content of the above component will change. But the div element still has the message class. The reason for that, as mentioned, is that React compares the content produced by the List component with its own Virtual DOM. Because they are equal in terms of DOM structure, it will not change the output structure of the component and will only update the parts that have changed.

Now we will change the above component like this:

import React, {useEffect, useState} from "react";
import Button from "./Button";

const Counter = () => {
   const[counter, setCounter] = useState(0);

   useEffect(()=>{
    console.log("Render Counter Component")
   })

   const btnClick = () => {
       setCounter(counter+1)
   }   

   const wrapperElement = () => {

        const wrapper = 
            <div id="wrapper">
                <span>{counter}</span>
                <Button onClick={btnClick}></Button>        
            </div>

        return wrapper;

   }

    return(    
        wrapperElement()
    )

}

export default Counter

Enter fullscreen mode Exit fullscreen mode

Here, if we add the mentioned class to the div element again and then click on the button, we will see that the message class will be removed from the div element. The reason is that the HTML structure is not the same as Virtual DOM and React will re-render the desired component with new changes.

Note: To see DOM changes, you can enable Paint flashing in Chrome browser from Developer Tool > More tools > Rendering and see the changes visually.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .