React's Three Phases: Complete Guide to Trigger, Render, and Commit
Understand the React rendering process and how components are updated in the DOM, providing insights into optimizing performance.
Introduction
Did you know that a React
component can re-render, while the corresponding displayed UI
segment has not been changed?
I know when we say rendering, we instinctively think that something has to be printed on the screen. But that’s not necessarily the case for React
🤯.
Why DOM Updates Are Expensive
Updating your application’s UI
which displayed in your browser screen, involves performing a series of operations on the DOM
or the Document Object Model
which is an object representation of the parsed Html
code that is required to display the UI
.
If React
was updating the DOM
every time a component has re-rendered, the number of DOM
performed operations can become huge. Therefore, your application’s performance would be negatively affected.
Moreover, we shall say that Updating the DOM
is considered a costly operation in general. Especially if performed too often because of the following reasons:
Browser Reflows
- First,
DOM
operations trigger browser reflows during which it has to re-calculate the layout of the entire page, or a large part of it. For example, when an element gets removed from theDOM
, the position of the other elements need to be recalculated again. The more elements on the page, the more expensive these re-calculations become.
Browser Repainting
- Secondly, After
reflow
, the browser may need to repaint the affected parts of the screen. Repainting involves redrawing elements (borders, shadows, colors, shapes… and so on). Despite being lighter thanreflow
it’s still not counted as a trivial operation.
DOM Updates are Synchronous
- Finally, in addition to
reflow
andrepaint
,DOM
updates are synchronous which means that they can blockUI
interactions and ruin the entire user experienceUX
.
Well, fortunately React does not touch the DOM
when a component renders. Rendering is nothing more than the process of calling your function component by React
, which is way faster than DOM
updates.
The Three Phases
Instead of updating the DOM on every render React
is splitting the work into 3 phases: trigger, render and commit. The DOM
updates are deferred to the last phase (the commit phase).
1. Trigger Phase
The trigger phase consist of asking React to render or re-render a specific component.
A render can be triggered by two different reasons:
Initial Render:
Firstly, when the whole component tree get initially rendered.
As you may know your app gets initially rendered via the React
root element which is created by the createRoot
function that is imported from react-dom/client
.
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
const root = createRoot(document.getElementById('root')!);
root.render(
<StrictMode>
<App />
</StrictMode>,
)
The createRoot
function receives the wrapper DOM
node, where the final rendered Html
that is corresponding to your component’s tree need to be injected (root div
). And then renders your components tree given the parent App component.
So the first render happens when the React
root render method gets initially called.
State Updates:
Secondly, Once your components tree has been initially rendered you can trigger further renders by calling the setter function returned by the useState
hook.
Whenever the setter function is called, React
queues or schedules a future render.
Even if you’ve called the setter function multiple times, the new state won’t be reflected until the next render.
Put diffrently, Using the setter function means just asking React
for a future render while mentioning the state changes.
React
will take that into consideration, and schedule the next render with the new state value. Consequently, it will update the parts of the JSX
code which are derrived from the new state during the next render.
2. Render Phase
Now let’s move into the second phase that comes after triggering a render, which is obviously rendering.
To put it in simple words, **rendering is just when React
decides to call your function component.
As we saw earlier, a render can be triggered either on app start during which the root object triggers the initial render of the whole component tree or on demand when a state gets updated in a specific component.
When a specific component’s state change, all of its direct or indirect children will re-render recursively. In other terms if the grandpa re-renders all of the descendant family will get re-rendered sequentially starting from the children to the grandchildren and so on.
Each component in the tree will return the JSX code, that corresponds to the UI segment that is responsible for.
As we saw in the JSX article, Despite looking like Html
code it’s just a Javascript
language extension meaning that it’s getting converted into a bunch of objects representing the real Html
DOM nodes.
So during the initial render of the component’s tree, React builds a virtual representation of the real DOM
as raw Javascript
objects.
When a given component re-renders because of a state update, the component and its descendant will re-render as we saw previously, so the JSX
returned by some components may differ compared with the initial re-render
React
will keep track of all the changes, that are caused by the re-renders, while calculating a minimal number of DOM
operations that are needed to move from the previous state (before rendering) to the newest state (after rendering).
3. Commit Phase
After the process of re-rendering during which React
creates a bunch of objects representing the DOM
elements and calculates many information regarding the minimal required DOM
operations to get from the oldest state to the newest.
React
will finally start modifying the DOM
during what is called the commit phase.
The commit phase, happens in two cases:
Initial rendering:: Just after rendering or calling all the components in the tree, React
reads the collected information that consist of, remember, the constructed objects representing the DOM nodes. And then uses the DOM
API to insert all the nodes inside the wrapping div
whose id
is equal to root
.
When re-rendering:: React
will use the information collected during the rendering phase and apply a minimal number of DOM
operations in order to make it match the latest rendering output.
So React
is smart enough to change only the DOM
nodes that have been changed during the previous phase.
Conclusion
By delaying DOM
operations to the commit phase and calculating the optimized number of DOM operations after re-rendering.
React
manages to achieve great performance while simplifying the process of UI
development by taking care of the complex stuff like DOM
manipulation and providing you the user, with a simple and descriptive language that allow you to model the UI
as a function of its state using React
components.
UI = fn(State)
in the next article, we will be diving more into the concept of State
in React
.
Thank you for your attentive reading and happy coding 🧑‍💻.