Hey there! ππΌ
I'm Mateo Garcia, I co-organize a community in Medellin, Colombia called React Medellin. This year I started a series of posts called 'Coding with Mateo'; My first post was about 6 ways to write a React component.
Today I want to share with you what I have researched for a couple of weeks about how rendering works in React. To begin with, I will say that the concept of rendering in React is a little different from how we know it. Let's find out why.
Table of Contents
1.Introduction
2.VirtualDOM
3.Render
4.Reconciliation
5.Commit
6.An example
Introduction
You are probably here because you have worked with React, interacted with their APIs, changed the state of your components, and seen the magic happen. However, it is sometimes useful to go into a little more detail about how React does what it does. Performance problems can appear when your React application is continuously re-rendering, the application has scaled considerably and, the components are complex and expensive to represent. That's why Understanding rendering in React
is something that all of us who use this library should understand.
To understand why React is so fast it is important to know these four concepts:
- Virtual DOM.
- Render.
- Reconciliation.
- Commit.
Let's get started
VirtualDOM
The Virtual DOM was a strategy that appeared to solve the modifications or mutations that the DOM suffers when using a web or mobile application. Rendering the entire document tree is too costly as applications become more complex; by mutations, we can understand any change that the DOM can undergo: an insertion/modification/deletion of an element or its properties.
Thus, the Virtual DOM came to represent the DOM tree in memory. Perform calculations using the state and props and finally decide which elements of the actual DOM (the browser one, I mean haha) should be mutated. From the official React website:
The virtual DOM (VDOM) is a programming concept where an ideal, or βvirtualβ, representation of a UI is kept in memory and synced with the βrealβ DOM by a library such as ReactDOM. This process is called reconciliation.
Initially, I said that the concept we normally know as rendering is different in React, I personally considered rendering as the procedure of synchronizing changes in the DOM. React synchronizes the changes in the DOM through three steps.
Render
Rendering is a process that is triggered by a change of state in some component of your application, when a state change occurs React:
- It will collect from the root of your App all the components that requested a re-render because their state or their props changed.
- It will invoke these components
- If you use
function components
it will invoke the function itself, something likeHeader(props)
. - If you use
class components
it will invokeYourComponent.render()
.
- If you use
Even when the process's name is rendering, at this point, the DOM has not been modified or altered, which could be a little tricky if you think as I did, about the meaning of render.
Since we normally use JSX
, the code will be transformed to React.createElement(...)
. The output of the createElement
will describe how the application should look like in the next version of the render through the next stage called:
Reconciliation
Once the re-rendering has happened, React has the context of two versions of the React.createElement
output, the version executed before the state change occurred, and the version executed after the state has changed.
At this point two objects are describing the UI, React through a heuristic algorithm of order O(n^3) will be able to determine which elements need to be represented again.
Among technical details the React team tells us some aspects about how React identifies which elements were affected:
Elements that changed type must be recreated.
Changes within the attributes of an element are replaced, without unmounting the element.
Upgrades within the element's children recreate all children
Updates within child elements that use
key
as attributes are compared and only new items are represented.
Commit
After React calculated all the changes that should be made in the application tree, react-dom
appears for the browser and react-native
for the mobile platforms, which make the modifications to the browser or mobile app API (finally! π₯³). Synchronously React will clean up the past layout effects, run the new layout effects, then the browser will paint the DOM, after that, React will clean up the past effects and mount the new ones; when I talk about effects I refer to the lifecycles method such as useLayoutEffect and useEffect.
To explain the lifecycles method part a little bit more, I bring to you this wonderful graph that Donavon West and his contributors created. This is the project repo, check it out!
Before moving on to the example, it is important to understand that under normal conditions, if a component calls render
, it will automatically do so for all its children. However it is possible to prevent certain components from being re-rendered under certain special cases, I have in my plans to talk about it, however, you can read about React.PureComponent
, React.memo
, React.useMemo
, and React.useCallback
.
Example
Consider the following example.
Here's the code
import * as React from "react";
import { useRenderTimes } from '../../utils';
function getRandomHEX() {
return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
}
function Header() {
const [color, setColor] = React.useState("#111");
const count = useRenderTimes();
return (
<header style={{ backgroundColor: color }}>
<p>Header component has re-rendered {count} times</p>
<button onClick={() => setColor(getRandomHEX())}>Click</button>
</header>
);
}
function Footer() {
const count = useRenderTimes();
return (
<footer>
<p>Footer component has re-rendered {count} times</p>
</footer>
);
}
function App() {
const count = useRenderTimes();
return (
<>
<Header />
<main>
<p>Hey, nice to see you again ππΌ</p>
<p>The App component has re-rendered {count} times</p>
</main>
<Footer />
</>
);
}
export { App };
}
useRenderTimes
is a hook that will allow us to accumulate the number of times a component is re-rendered. I saw it in a post by Kent C Dodds, so thanks!
import * as React from 'react';
function useRenderTimes() {
const renderRef = React.useRef(0);
React.useEffect(() => {
renderRef.current = renderRef.current + 1;
});
return renderRef.current;
}
export { useRenderTimes };
The <Header />
component has its own state, which will be changing once we start pressing the button. Let's take a look
What just happened here is:
- An event in the
<Header />
component triggered a state change. A render was scheduled. - VirtualDOM started analyzing which components were marked as needing to be re-rendered. Only
<Header />
needed it. - Through the reconciliation step, it was identified that the style of the
<header></header>
was changing. - Dispatched a commit to DOM.
- Boom, we see the change of the background color.
Final thoughts
Although rendering in React is a process that can become complex, we must recognize the excellent work done by the entire React Team to improve the day to day experience in web development. Knowing the deeper parts of a tool can be useful for people who are just starting to discover it, as well as for people who have been using it for a long time and want to understand what was going on behind the scenes.
I want to thank the experts who continually strive to share all the knowledge in the most understandable way possible, some of them are Mark Erikson and Kent C Dodds. I leave you the link to their blogs. Each article is a gold mine and needs to be recognized.
If you found this post useful and would like to see more content, you could react to this post, which would make me very happy. If you have any comments or corrections that could improve this post, I would be glad to receive them. Thank you for your time ππΌ π.
Top comments (25)
I think the heuristic algorithm's time complexity drops to O(n) with some constraints. reactjs.org/docs/reconciliation.ht...
You're right π€
Great article!
Thank you, Adarsh :)
How Virtual DOM is mapped with Real DOM on the first Render? Isn't it time consuming to do all this conversion from JSX to JS and then create a virtual DOM and map it with the real DOM? I want to understand how initial render happens?
I have the doubt if the new Virtual DOM is created after or before re-rendering. According to the order of the processes it seems that first is the Virtual DOM and then the re-render, but I always thought it was the other way around, because how do you know what new elements the new Virtual DOM will have without doing the re-render? Thank you.
NICELY EXPLAINED!!!
Thank youu :)
If anyone's interested, here's a more in-depth coverage of the process described in the article indepth.dev/posts/1008/inside-fibe...
Thank You for this Article!!Well framed!
Good job! Thank you for this article!)
Thank you, Alexander :)
Perfectly explained and interesting topic! Well done π
Thank you for reading it, Killian :)
thank you!