The Subtle Aspects of React Re-rendering (Part 1): Functions Calls and Lazy Evaluation
Like many web companies we use React at Doctolib, and like all web companies we like a website that renders fast.
The best way to improve performance, in a website or anywhere else, is to avoid doing work. This is exactly what React rendering optimizations are for: rendering just the components that need it, and nothing more.
As a developer, if you understand React re-rendering optimizations, you will:
- benefit from performance gains on your website,
- stay away from bugs arising when we get in the way of React,
- better understand how React works.
In this two-post series on React re-rendering, you will learn some of the subtle aspects of React re-rendering:
Part 1 — Functions calls and lazy evaluation
Part 2 — Dynamic children, memos, and constant functions
All the contents I present in this series on React re-rendering have been taught to us by Romain Pellerin, one of our Principal Engineers at Doctolib.
React destroys and remounts components
Consider the following code:
This code is a React component containing an if statement and two return statements. If
false, then only the first return statement is executed, otherwise only the second return statement is executed.
Note that the first return statement calls the
showPasswordInput function, whereas the second return statement calls the
Those two functions happen to look a lot like one another:
They both return an
<input> html component, with different properties.
It all looks good except that, after logging in, this code displays the user’s password in plain text!
To understand why, observe that both return statements in
<Page> share a common structure: they both contain an
<h3> that contains a
<Layout> that contains an
Now consider the following scenario:
- The user is not logged in. The first return statement gets executed, React displays the
- The user types their password in, the characters are hidden.
- The user gets logged in.
- The component is re-rendered. The second return statement gets executed. As an optimization, React keeps the same
<input>field, just changing its type. But it keeps the rest of the parameters, including its value!
- As a result, the input fields show the password in plain text. 😱
How to fix it
The reason why the problem is happening is that we used directly html components for the
<input>s, as opposed to encapsulating them in React components.
An easy way to fix it then is to wrap our
<input>s in React components. We start by capitalizing the function names to follow React components' convention. And the term “show” is no longer relevant for a component:
And we make the calling code invoke the components by surrounding them with
Note that even though the
<input> parameters were already returned by functions in the first example, this wasn't enough for React to consider them as independent entities. We have to call them with the component syntax for React to consider them as independent components, and to re-render them entirely, including the values they contain.
React has lazy evaluation
Now consider the following code, that calls our
App component invokes the
showComments function, defined here:
The value returned from
showComments is passed as a child to
Page uses its
children only in its
else block, when
But the fact that we’re calling a function, and not a React component, has the effect that
showComments is called regardless of the value of
false, the code of
showComments will therefore have no effect on the display of the page, but it wastes rendering time.
If we transform this function call into the invocation of a React component, like this:
then React only renders this component when it needs it, that is in the second return statement. So if
<Page>, the code of
ShowComments is not called.
The fact that React delays rendering until knowing whether it is needed is called “lazy” evaluation.
Those two examples allow us to realize that React does more than displaying a components tree. It has optimizations in order to render as little components as possible, thus making the rendering as fast as possible.
However, to benefit from those optimizations and to keep a correct behaviour, we need to play by React rules: defining and calling our entities as React components, and not just with functions calls.
In the next post of the series, we’ll dig into other React optimizations: the cases of Contexts, memos and constant functions.
If you want more technical news, follow our journey through our docto-tech-life newsletter.
And if you want to join us in scaling a high traffic website and transforming the healthcare system, we are hiring talented developers to grow our tech and product team in France and Germany, feel free to have a look at the open positions.