Understanding How Rendering Works - A Really Deep Dive

Do you really understand React Render Phase and Commit Phase?

Β·

10 min read

Understanding How Rendering Works - A Really Deep Dive

Get Started

Welcome back to Web Dev Distilled!

It's Howard here.

This article will shed light on one of the most fundamental and vital knowledge about how exactly React renders our application behind the scenes.

I can't emphasize it enough if you truly want to master React.

Without further ado. Let's get started!


A High-Level Overview of How Components Are Displayed on The Screen

This process is started by React each time a new render is triggered.

Most of the time by updating state somewhere in the application.

State changes will trigger render. So, the next phase will be render phase.

Render Phase

In this phase, React calls component functions and then figures out how DOM should be updated!

However, it does not UPDATE the DOM in this phase.

😡 We're familiar with the term "Rendering" = displaying elements on the screen. Just because it's easier to understand, and also makes sense.

However,

πŸ€” React definition of Render is way different from what we usually think about "Render". It can be quite confusing here.

Rendering in React is NOT updating the DOM, or displaying elements on the screen.

Rendering ONLY HAPPENS INTERNALLY Inside React.

It does NOT produce any visual changes in this phase!

Commit Phase

When React knows how to Update the DOM from the Render phase. It will do so in the Commit Phase.

In this phase, React actually writes to the DOM, updating, inserting and deleting elements to update the UI accordingly with the current State of our application.

The Commit Phase is what really responsible for what we call "Rendering" not the Render phase.

Finally, the browser will notice the DOM has been updated, then it repaint the screen!

This final step has nothing to do with React anymore, but it will produce visual changes on the screen that users see.

With this high-level overview in mind, let's zoom in on each phase in the entire process.


1. How Renders Are Triggered?

The TWO situations that trigger renders:

  1. Initial Render of the application

  2. State is updated in one or more component instances (Re-render).

πŸ‘‰ The render process is triggered for the ENTIRE APPLICATION

Not just for one single component.

That doesn't mean the entire DOM is UPDATED.

Because in React Render Phase, it's just about calling the components function and figures out what needs to change later.

πŸ‘‰ In practice, it looks like React only re-renders the component where state update happens, but that's NOT how it works behind the scenes.

πŸ‘‰ Renders are NOT triggered immediately, but scheduled for when the JS engine has some "free time". There's also batching of multiple setState calls in event handlers.


2. THE RENDER PHASE

There are some Myths about the mechanics of State in ReactπŸ€”

  1. RENDERING IS UPDATING THE SCREEN / DOM

    However, it's technically NOT TRUE.

    Rendering is NOT about the Screen or the DOM or the View.

    It's just about calling component functions.

  2. REACT COMPLETELY DISCARDS OLD VIEW (DOM) ON RE-RENDER.

    In fact, the DOM will not be updated for the ENTIRE component instance.

If these 2 things are not true. Let's talk about How Rendering actually works.

At the beginning of Render Phase.

React will go through the ENTIRE component tree, take all the component instances that triggered re-render, and then actually render them, which simply means calling the corresponding component functions that we defined in our code.

πŸ‘‰ This will create Updated React Elements.

πŸ‘‰ This altogether makes up the New Virtual DOM.

2.1 - Virtual DOM

On Initial Render

React will take the ENTIRE Component Tree ➑️ and TRANSFORM it πŸ’…πŸ» into one BIG React Element Tree.

THIS is what we call THE VIRTUAL DOM. βœ…

It's sounds a bit fancier than say: "React Element Tree" 😜

Virtual DOM:

is the Tree of ALL REACT ELEMENTS created from ALL INSTANCES in the Component Tree.

πŸ‘‰ Cheap and Fast to create multiple trees. Even if we need many iterations of it.

Because in the end it's just a JavaScript object.

If you're not familiar with React Elements, make sure to read my previous article with a deep explanation of Component, Instances, and Elements.

I highly recommend you check it out HERE. 😎

πŸ‘‰ Has NOTHING to do with the "Shadow DOM"

Shadow DOM is actually just a browser thing, not React thing.

It's browser technology we use for stuff like Web Component.

Let's now suppose there will be a State update in Component B

React will go ahead and CALL the function of component B again.

NEXT, it will place the NEW React Element created by component B in a NEW React Element Tree - a new Virtual DOM.

🚨 Caveat: Rendering a component will cause all of its child components to be rendered as well, all the way down of the Component Tree. No matter if props we passed down have changed or not.

Therefore, all child components of B, such as E and F will be re-rendered as well.

It might sounds Crazy!! 😡 πŸ‘Ž

BUT it's necessary because React doesn't know whether children will be affected or not.

By default, React choose to play in safe, and just render everything.

Keep in mind that, this DOES NOT mean that the Entire DOM is Updated!

It's just a Virtual DOM that will be recreated!

Next step, the New Virtual DOM just created after state update will get Reconciled with the Current Fiber tree.

The RESULT of this reconciliation process is gonna be the UPDATED FIBER TREE.

This is a Tree that will ultimately be used to write to the DOM. 😁

2.2 - WHAT IS RECONCILIATION?

❓ Why not update the ENTIRE DOM whenever state changes somewhere in the app?πŸ€”

Because that would be INEFFICIENT and WASTEFUL

1️⃣ Writing to the DOM is relatively SLOW.

Creating new React Elements for an entire app is cheap and fast, because it's just a JavaScript object.

Writing to the actual DOM is expensive.

2️⃣ Usually only a SMALL part of the DOM needs to be updated.

So it's extremely wasteful to write all the virtual DOM to the real DOM each time render was triggered.

Thereby, React try to reuses as much of the Existing DOM as possible.

BUT, HOW React actually do that?

How React know what changed from one render to the next one?

That's where Reconciliation comes into play!

πŸ“š Reconciliation: Deciding which DOM elements actually need to be inserted, deleted, updated, in order to reflect the latest state changes.

It's a process by Reconciler.

πŸ’™ Reconciler is really the Engine (or the Heart) of React. It allows us to NEVER touch the DOM directly, instead simply tell React what the next snapshot of UI should look like based on State.

The current Reconciler of React is FIBER

During the Initial Render of the application.

Fiber takes the entire React element tree (Virtual DOM) based on it, and builds another tree, which is the FIBER TREE.

πŸ‘‰ Fiber Tree: an internal tree that has "Fiber" for each component instance and DOM element.

πŸ‘‰ Unlike React Elements in the Virtual DOM. Fibers are NOT re-created on every render.

πŸ‘‰ It's a mutable Data structure , once created from initial render, it's simply mutated over and over again in Future reconciliation process.

It makes Fibers the PERFECT place for keeping track of things like:

πŸ‘‰ Current State

πŸ‘‰ Props

πŸ‘‰ Side Effects

πŸ‘‰ Used Hooks

πŸ‘‰ Queue Of Work such as: updating state, refs, performing DOM updates, so on...

πŸ‘‰ Fiber is also defined as a "UNIT OF WORK".

Instead of the normal parent-child relationship, Fiber tree has a structured of a Linked list, it makes it easier for React to process the work that is associated with each Fiber.

πŸ™‹ One extremely important characteristic of Fiber is Work can be done Asynchronously.

πŸ‘‰ The rendering process can be split into chunks, tasks can be prioritized, and work can be paused, reused, or thrown away.

πŸ‘‰ Enables concurrent features like Suspense or transitions.

πŸ‘‰ Long renders won't block the JS engine. It's important for large application performance!

Reconciliation In Action

Code Example:

<App>
    <Video />
    {showModal && (
        <Modal> 
            <Overlay>
                <h2>Rate the video </h2>
                <button>🌟</button>
            </Overlay>
        </Modal>
     )}
      <Btn>
        {showModal ? "Rate" : "Hide"}
      </Btn>
</App>

For the above example, we updated the showModal state variable from true to false

State updated in the App , therefore the Video and Btn Components will be re-rendered as well.

Whenever the reconciliation needs to happen, Fiber walks through the entire tree step by step, then analyzes exactly what needs to change between the Current Fiber Tree and the Updated Fiber Tree based on the New Virtual DOM.

This process of comparing elements based on position in the tree call Diffing.

I will have another article on that very soon.

Updated Fiber Tree - workInProgress Tree.

Please have a look at the diagrams above. πŸ‘†

Firstly, Btn component has new text, changed to Hide , the work needs to be done in Fiber is a DOM Update.

Secondly, we have Modal, Overlay, h3, button these were in the current fiber tree (before State update), but no longer in Virtual DOM, so they are marked as DOM Deletion.

Lastly, we have the Video component was re-rendered because it's a child of App , BUT actually it did not change. As a result, the DOM will not be updated in this case.

Once the process is over, all of the DOM mutations will be placed in a list called "The List Of Effects" which will be used in the Next Phase.

That's the final result of this phase.

At this given moment, React Still HASN'T WRITTEN ANYTHING to the DOM yet.

All the DOM operations will be made in the COMMIT PHASE.

Let's get into it!


3. THE COMMIT PHASE

βœ… In this phase, React writes to the DOM: insertion, deletion, updates...

React goes through the effects list that was created during the Render phase, then applies them one by one to the actual DOM Elements that were in the already existing DOM tree.

βœ… The Commit Phase is synchronous, Unlike Rendering phase that can be paused.

πŸ‘‰ Writing to the DOM happens all in one go, it can't be interrupted.

This is crucial to make the DOM never show partial results, ensuring the consistent UI (in sync with state at all times)

βœ… After the commit phase completes, the workInProgress fiber tree becomes the current fiber tree for the next render cycle. That's make sense, right? 😁

Fiber trees are never discarded or re-created from scratch.

It's reuse to save precious rendering time. 😡

Let's continue.

Now we finished the COMMIT PHASE.

The browser now notices the DOM has been changed. As a result, it will repaint the screen, whenever it has Idle time.

So now, the updated DOM finally visible in a form of an updated user interface.

The Commit Phase is performed by a separate library that writes to the DOM, it called React DOM.

React DOM is also called "Renderers". It's quite a terrible name!

Because React DOM - "Renderers" DON'T RENDER. It just commits the result of render phase to the DOM.

The Render Phase does not touch the DOM. React Library itself is performed just in renders. It does not know where the rendered result will go.

Because of this reason, React can be used on different platforms (hosts).


That's it.

It's quite a deep dive on how React Rendering works.

Internally inside React, it's way more difficult than what I just showed you.

But knowing all of this is enough for you to accelerate your React mindset to be top 10% React developers.

I put a lot of time and energy into it. Hope you enjoy!

Please consider subscribing to my newsletter. Please 😜

See you soon.

*Reference resources:*Jonas Schmedtmann & React Documentation.

Β