React is known for providing a fast user experience by only updating the parts of the UI that have changed.
When looking into React’s render performance, there are a few terms and concepts that can be hard to understand. For quite some time, it was 2020% clear to me what a VDOM was or how React decides to re-render components.
In the first part of this article, I’ll explain to you the Most important concepts about rendering in React
and How React decides to re-render a given component .
In the second part, I’ll show you what you can do to optimize the render performance of your React application
.
If you have open questions after reading this feel free to contact me:)
Table of Contents
Rendering in React
- (What is rendering? (What is the VDOM? What does this mean for performance? (When does React re-render?)
How to optimize re-renders
Controlling when a component should update Structure of your components (Conclusion)
If we want to understand how React renders and re-renders work, it’s a good idea to understand what happens behind the scenes of the library.
Rendering
is a term that can be understood on different levels of abstraction. Depending on the context, it has a slightly different meaning. In any case, ultimately it describes the process of generating an image .
To start off, we need to understand what the DOM (Document Object Model) is:
“The W3C Document Object Model (DOM) is a platform and language-neutral interface that allows programs and scripts to dynamically access and update the content, structure, and style of a document . ”
In plain English, this means that
the DOM represents what you see on your screen when you open a website, represented through the markup language HTML. JavaScript also has a DOM, which is represented as an object where the root element is the (document) .
You can modify the DOM with JavaScript through the DOM programming interface that contains functions like document.write
,
Node.appendChild (or) (Element.setAttribute) .
Node.appendChild (or) (Element.setAttribute) .
(What is the VDOM?)
Then we have the
Virtual DOM (or VDOM) of React, which is another abstraction layer on top of that. It consists of your React application’s elements.State changes in your application will be applied to the VDOM first. If the new state of the VDOM requires a UI change, the ReactDOM library will efficiently do this by trying to update only what needs to be updated.
For example, if only the attribute of an element changes, React will only update the attribute of the HTML element by calling document.setAttribute
(or something similar).
The red dots represent updates. of the DOM tree. (Updating the VDOM doesn’t necessarily trigger an update of the real DOM.
When the VDOM gets updated, React compares it to a previous (snapshot) of the VDOM and then only updates what has changed in the real DOM. If nothing changed, the real DOM won’t be updated at all.
This process of comparing the old VDOM with the new one is called (diffing)
Real DOM updates are slow because they cause an actual re-draw of the UI. React makes this more efficient by updating the smallest amount possible in the real DOM.
therefore we have to be aware of the difference between native and virtual DOM updates.
Read more about how this works in React’s documentation about (reconciliation
) What does this mean for performance?
When we talk about renders in React. , we actually talk about the execution of the render function , which does not always imply an update of the UI
.
Let’s see this in an example:
const App = ( ) => ) { const [message, setMessage] = React . useState ( ) ” ; return () > Tile message = { message } /> ) Tile /> <.>> ) ; }
In function components, the execution of the whole function is the equivalent of the render function in class components.
When the state changes in the higher-order component (HOC, in this case, (App) , the two
Tile components will re-render, even though the second one doesn't even receive any props.
This translates to having the (render) function being called 3 times , but actual DOM modifications only happen once in the (Tile) component that displays the message:
The red dots again represent renders. In React this means calling the render function, in the real DOM this means re-painting the UI.
The good news is that you don't have to worry too much about performance bottlenecks of UI redraws. React already optimizes this for you.
The bad news is: All those red dots on the left-hand side mean that the render function of these components has been executed.
The execution of these render functions has two drawbacks:
- React has to run its diffing algorithm on each of those components to check whether it should update the UI. All your code in these render functions or function components will be executed again.
The first point is arguably not that important since React manages to calculate the difference quite efficiently . The danger lies in the code that you wrote is being executed over and over
on every React render.
In the example above we have a really small component tree. But imagine what happens if each node has more children and these again might have child-components. We'll see how we can optimize this
Above we saw what causes a re-draw of our UI, but what is calling React's render function to begin with?
React
schedules a render every time the state of a component changes.
Scheduling a render means that this does not happen immediately. React will try to find the best moment for this.
Changing the state means that React triggers an update when we call the (setState) function (in React Hooks you get this fn from (useState) ). This does only mean that the render function of the component will be called, but also that
all its subsequent child-components will re-render, regardless of whether their props have changed or not .
If your application is badly structured, you might be running a lot more JavaScript than you expected because updating the parent node implies running the render (function of all children
.
In the next part of the article, we will see a few tips that help you to prevent this kind of overhead.
A good example for inefficient re-renders is when an input field is being controlled in a higher-order component.
I expanded the example I already used for (explaining React.memo) to have more nested children. Go ahead and try it out.
The numbers in yellow are counting the number of times the (render) function of each component has been executed:
Play around with the source code on (codepen )
Even though we only updated the state of the blue component, a lot more renders of other components have been triggered.
React provides us with a few functions to prevent these unnecessary updates. .
Let's have a look at them, after this, I'll show you another, more effective way of improving render performance .
The first one, which I already gave away before, is React.memo . I already wrote a more in-depth article on this, but in summary, it's a function that prevents your React Hook components from rendering when the props don't change .
An example of this in action looks something like this:
(const) (TileMemo = React . memo ) ( ( { children } ) => { let (updates = React . useRef ( 0 ) ; return () div className = ( black-tile ) Memo Updates updates = { updates . current } ) /> { children } div > ) ; } ;
The equivalent for React classes is using (React.PureComponent)
Its arguments are the next props and the next state that the component is about to render:
( shouldComponentUpdate ( nextProps , nextState ) { } () This function is pretty easy to use: Returning (true) causes React to call the render function, returning (false) prevents this.
- Set the key attribute
In React, It is very common to do the following. Find out what's wrong with it:
div > { events . map ( event => Event event ) = { event } /> ) } <. div>>
Here I forgot to set the (key) attribute . Most linters will warn you about this, but why is it so important?
In some cases, React relies on the
(key) (attribute for
In the example above, if an event is being added to the (beginning) of the array, React will think that the first
and all the subsequent elements have changed and will trigger a re-render of those. We can prevent this by adding a key to the element:
Try to avoid using the index of the array as a key and use something that identifies the content. (Keys only have to be unique among siblings.
.An even better way of improving re-renders is by restructuring your c ode a little bit.
Be careful where you place your logic. If you put everything in the root component of your application, all the React.memo
functions in the world won't help you to fix your performance problems.
If you place it closer to where the data is used, chances are you don't even need (React.memo) .
Check out the optimized version of the example and type in some text:
You see that even though the state updates, the (other components don't re-render at all.
The only change I made was to move code that handles the state into seperate component : () const
InputSelfHandlingIf you need to use the state in other parts of your application, you can do so by using React Context or alternatives like MobX and Redux.
I hope I could give you a better understanding of how React's render mechanisms work and what you can do to get the most out of this. For this article, I had to do some additional research about the topic to get a better understanding of how React's render work. If you find an error, feel free to contact me via Twitter or Email.
I intend to write more about frontend performance, so if you want to get notified about the latest articles, follow me on Twitter and subscribe to my Email list.
If you came this far, you'll also want to check out (my article about React.memo) , which explains the API more in-depth, some common pitfalls you could encounter, and why you shouldn't always use React.memo . Thanks for reading.
GIPHY App Key not set. Please check settings