Unlocking React's Inner Workings: A Beginner's Guide

Unlocking React's Inner Workings: A Beginner's Guide

Photo by Josh Redd on Unsplash

React has rapidly become one of the most widely used web frameworks, thanks to its robust community and powerful features. In this beginner's guide, we will delve into the inner workings of React to help you gain a deeper understanding of its core mechanisms and write better, more efficient code.

Why should you care about how React works?

While learning React, we become familiar with numerous "rules" for writing React code, such as:

  1. Use keys when utilizing map to output a list of similar elements.

  2. If a parent element renders, all the child elements beneath it re-render as well.

  3. And so on.

Most beginner React developers are aware of and apply these rules. By understanding the reasons behind these rules, you can gain an immediate advantage over the majority. This understanding will also enable you to write superior React code, enhancing performance, user experience, and developer experience by crafting cleaner code.

❤️ The Heart of React - the Virtual DOM

What is the DOM?

As a front-end web developer, one of the most important concepts you will ever learn is the DOM or the Document Object Model.

Let me define it simply: DOM is a tree-like interface where each element, attribute and text is represented by a node. We can use JavaScript to manipulate these nodes by adding, removing or modifying them.

So, we can visualize an HTML document as a Lego building made of various blocks. We can add, remove or change the properties of the blocks (like the colour) by using JavaScript and thus, change the building as a result.

What is the Virtual DOM and why do we need it?

When the DOM is changed by affecting any one of its nodes, the browser recalculates the layout and repaints the affected elements. Although modern browsers have optimized the recalculation techniques to ensure only the affected elements are recalculated, you can still notice performance issues if there are multiple, continuous layout recalculations. As you can imagine, this would make more complex apps very slow. That's where the virtual DOM comes in.

The virtual DOM is just a copy of the actual DOM. Its main advantage is that it is decoupled from the browser. A change in the virtual DOM does not trigger any change on the screen by the browser. This means that there is no recalculation or repainting required. This makes the virtual DOM much faster than the actual DOM.

How does React use the Virtual DOM?

There are two steps in React's DOM updation process:

  1. Diffing

  2. Reconciliation

Let us look at each of them in sequence.

Diffing

When React renders the app for the first time, it creates a Virtual DOM representing the entire UI tree. Now, when an update is needed due to changes in the app's data state, a new Virtual DOM is generated with the updated UI.

React then compares the old and the new virtual DOMs to identify the changes between the two. This process is called diffing.

The next step is reconciliation.

Reconciliation

Once the diffing process is complete and React has identified the nodes that need to be created, updated, or deleted, it must display these changes on the screen for the user. In other words, it needs to modify the actual DOM.

The process of altering the actual DOM based on the results of the diffing process is known as reconciliation in React.

A visual representation of how diffing and renconciliation works

Now that we have grasped the fundamental workings of React, let's examine how this leads to some of the guidelines we follow when writing React code.

🗝️ Keys

The Problem

When using map to render a list of similar components without a key, React relies on the position of each component during the diffing process to determine if any list items have changed.

Let us look at some code to understand the problem with this approach.

const nums = [1, 2, 3, 4, 5];

const NumList = () => {
  return (
    <ul>
      {nums.map((num) => (
        <li>{num}</li>
      ))}
    </ul>
  );
};

/******
Output:
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
</ul>
******/

If we modify the last item in the nums array, React will scan the entire array, notice that only the last item has changed, and consequently, update only the last element.

However, if we alter the first item in the nums array, React will observe that the first item in the array has changed and, as a result, conclude that the entire list has been modified, leading to a re-render of the whole list.

As you can guess, there are a few problems with this approach:

  1. React has to traverse the entire list every time it needs to perform a diff. This takes a significant toll on performance, especially for lists with a large number of items.

  2. If an item near the beginning of the list has changed, React will treat it as if the entire list has changed and, consequently, re-render the whole list. This, again, negatively impacts performance.

Keys to the rescue!

All of this can be avoided by simply providing a unique key to each list item. React then uses this key to determine if the list item has changed. It no longer relies on position. Instead, each list item is uniquely represented by its key, and as a result, the diffing only occurs between list items with the same key.

Therefore, if any list item changes, only that specific list item is updated.

const nums = [1, 2, 3, 4, 5];

const NumList = () => {
  return (
    <ul>
      {nums.map((num) => (
        <li key={num}>{num}</li>
      ))}
    </ul>
  );
};

/******
Output:
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
</ul>
******/

Using indices as key

A very common beginner mistake in React is using indices as key. This is considered an anti-pattern and should be avoided.

With our newfound knowledge of how React works with lists, we can understand why this is an anti-pattern.

  1. If the list is dynamic, i.e., the position of the list item can change - maybe via sorting, filtering, etc. - then using indices as key will re-render the entire list since the item at each position changes.

  2. If an item is added at the beginning of the list, then, again, the entire list re-renders as the item at each index changes.

Hence, in most cases, the optimizations and advantages we want when we use key is lost when we use indices as key.

Memoization in React

As we observed earlier, if React detects a difference in a specific node, the node and its entire subtree are re-rendered. This may result in some unnecessary renders that can be avoided by using memoization techniques in React.

You can read about the various memoization techniques available and when to use them in my post: React App Optimization: Mastering Memoization for Efficiency

Conclusion

In conclusion, understanding React's inner workings, such as the virtual DOM, diffing, reconciliation, and the use of keys, can greatly improve your coding efficiency and performance. By being aware of these core mechanisms, you can write cleaner and more optimized React code, giving you an edge over other developers and ensuring better user experiences in your web applications.