The Subtle Aspects of React Re-rendering (Part 2): Dynamic children, memos, and constant functions

Jonathan Boccara
May 26 · 5 min read

In the first post of our series on the subtle aspects of re-rendering in React, we saw how React behaves with function calls and lazy evaluation.

To dive deeper in React re-rendering we’ll see now how dynamic children, memos and constant functions impact rendering.

Understanding these aspects of React will make you reap performance gains for your website, stay away from a whole class of bugs, and get a deeper understanding of React.

To understand the following examples, you need to be familiar with React contexts.

Re-rendering in a hierarchy

Consider the following code:

Context contains a CountContextProvider component and passes it one child, the Parent component.

Here is CountContextProvider:

CountContextProvider puts a GrandParent inside of a React context providing the count state, and passes it CountContextProvider's children (here, Parent).

Let’s look at GrandParent:

GrandParent is a component containing the child it received. In our case this child is Parent:

Parent returns the Child component:

Child contains a button that applies the identify function on the count state coming from CountContextProvider all the way up.

Which components re-render when we click on this button? None!

Indeed, React recognizes that the function didn’t change the state’s value, and knows that there is nothing to re-render. Let’s make the function change the state:

Now which components re-render when we click on this button?

When we click the button, CountContextProvider, GrandParent and Child re-render:

  • CountContextProvider re-renders because its state, count, was modified
  • GrandParent re-renders because it is a hardcoded child of CountContextProvider which is re-rendering
  • Child re-renders because it accesses count, a modified state

These are the reasons that can make a component re-render: having its state (or props) changed, accessing a state coming from a context that changed, or being a hardcoded child of a component that re-renders.

However Parent does not re-render because it doesn't access count and it is a dynamic child of CountContextProvider: it was passed in its {children} property, as opposed to being hardcoded like GrandParent was.

Re-rendering of memos

Now let’s introduce a memo component in CountContextProvider:

Here is the code of our MemoComponent:

React.memo indicates to React that this is a pure component, meaning that it doesn't have or access state. As a result, React only re-renders it when its props change.

Now if we go back to our Child component and click its button, will our MemoComponent re-render?

The answer is that it does re-render. The reason is that its prop onClick has changed. Indeed, its source onClickMemo is redefined whenever CountContextProvider re-renders, and as we saw in the first example CountContextProvider does re-render when we click on Child's button.

To benefit from React.memo's effect and avoid re-rendering of MemoComponent here, we can use useCallback to keep the same function onClickMemo across all renderings of CountContextProvider:

useCallback takes two parameters: a function and a list of objects. When the component re-renders, useCallback returns the same instance of the function as in the previous rendering, unless one of the objects of the list has changed. Those objects in the list are called dependencies.

In our case, the list of dependencies is empty, so onClickMemo is constant across all re-renderings.

Now MemoComponent no longer re-renders when we click on Child's button, because its prop onClick coming from onClickMemo hasn't changed.

Constant functions and references

Note that in our last example with useCallback, we had to introduce the following lines of code in CountContextProvider:

and we used countRef instead of countin onClickMemo.

If we had kept using count, like this:

then we would have a bug! Since we keep the first instance of onClickMemo at every render, it would come with the first value of count in its closure. So at every render, the count used inside of onClickMemo would stay at 0, even though the new count coming from the context would be incremented.

References allow to work around this problem. A reference (here countRef) is a intermediary object pointing to a value (here countRef.current). The reference is constant at every render, meaning that each render uses the same reference, but nothing prevents the value it points to from changing.

So at every render we update countRef.current with the latest value of count, and even though onClickMemo always uses the same countRef, it can access the latest value of count via its .current key.

References and useEffect

useEffect is another common application of this principle, where we need to be careful and use references to have values up to date.

To illustrate the point, let's twist the code of ourMemoComponent to make it use useEffect. The resulting code is a contrived example as you wouldn’t create a button like this, but it’s only to expose the issue with useEffect if it creates something used later:

This code creates a button just as in the previous version of MemoComponent, but this time it associates its callback in a useEffect function, that is called only once because it has an empty list of dependencies.

This implies that the button of MemoComponent keeps the same version of onClick every time it re-renders (whether we define onClickDemo with useCallback or not).

If we use a countRef reference in onClickMemo then clicking MemoComponent's button correctly displays the latest value of count.

But if we use count directly in onClickMemo, only the first value of count, gets closed over in onClickDemo and only that first version of onClickDemo gets closed over in the function registered by useEffect. As a result the button will display this first value at all re-renders.

This is why it is important to use a reference and not a value in useEffect when it creates something that will be used in re-rendering (for example a callback).

React re-rendering

Re-rendering is at the heart of React, and a good understanding of it is essential for front-end and full stack developers.

This series on the subtle aspects of React re-rendering showed you how React behaves with function calls, lazy evaluation, dynamic children, contexts, memos and constant functions.

Those aspects are key to work in synergy with React, and have a fast website and with a correct behaviour.

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.

Doctolib

Improving Healthcare for Good

Doctolib

Founded in 2013, Doctolib is the fastest growing e-health service in Europe. We provide healthcare professionals with services to improve the efficiency of their organization, transform their patients’ experience, and strengthen cooperation with other practitioners. We help pati

Jonathan Boccara

Written by

Principal Engineer @Doctolib

Doctolib

Founded in 2013, Doctolib is the fastest growing e-health service in Europe. We provide healthcare professionals with services to improve the efficiency of their organization, transform their patients’ experience, and strengthen cooperation with other practitioners. We help pati

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store