DEV Community

Cover image for React Virtual DOM, Diffing, and Keys
smhaley
smhaley

Posted on

React Virtual DOM, Diffing, and Keys

This post is a quick run through of the React Virtual DOM and it's implications on using the key prop during development.

There are many reviews of this topic around the web. However I still see many newer devs make mistakes with keys. I hope my addition with interactive examples will add some clarity to the topic.

Check out this link for the interactive demos

The React Virtual DOM and Reconciliation

React is fast. Very fast. Part of its secret sauce is calculating all the changes that occurred (such as state and prop changes) in memory before applying them to the actual browser DOM.

In memory React keeps a virtualized copy of the the DOM. Whenever an event triggers a rerender, React compares the new state of the Virtual DOM with that of the previous via a diffing algorithm.

The algorithm then reconciles what has been updated with what has not and updates the browser DOM with all changes in batch. See this process in the image below.

reconciliation

Diffing

To understand diffing it helps to think of a React app as a tree:

diffing

The left tree diagram is a React app. The red node is single component (element) updating within the application.

React then updates all the 'child' elements below the updating element (see the right side). That is, both the red elements are rerendering or possibly remounting as a result of the top level change.

How does React decide to rerender or remount?

This is controlled by the core assumption of diffing

  1. Two elements of different types will produce different trees.

  2. The developer can hint at which child elements may be stable across different renders with a key prop.

So what does that mean?

React will fully remount a component when the actual element changes: such as <Component/> changing to <Component2/> or a <a> changing to a <div>.

This is reasonable. If the the component itself is different the diffing process completely unmounts the outdated element and remounts the new element. The catch is, everything below the unmounted element gets unmounted and remounted as well (all state is wiped out of each unmounted element). In this context, the change on the left causes both the red elements on the right to remount.

But what about rerendering?

If the diffing algorithm determines that the changes were only attributes on the element (such as props or state) it will only rerender the component that changed and all components below (that is why the image on the right has both elements as red).

The second assumption allows developers to let React know that a component has changed using the key prop. The key prop is often used in lists, but in the context of a component it will force the component to unmount and remount whereas the diffing algorithm was hinted about the change.

Okay let's look at a demo:

key example

In the above gif there is a Root (blue background), Child (Color Changing), a Deep Child (Pink), and a Tree. Each of these components is represented by the Tree diagram showing the state of mounts, remounts and rerenders.

When the gif starts all render counts are 1.
As the user updates the state of the Child component (by paginating), React diffing renders all components within the Child causing the render count to increase. Since there was just an attribute change, there was no remount.

Updating state at the Root (Update Element Attribute button) causes all the components in the Tree diagram to rerender--increasing the render count. This is because the Update Element Attribute button updates state at the root (color prop) and passes this new prop to the child.

It is not until the actual Change Element button is selected that the diffing algorithm realizes that the Child and everything below it must be unmounted and remounted. This is because the Change Element button updates a counter state in the Root component and passes this new count to the key prop of the Child. The diffing algorithm simply rerenders the Root due to the state change, but completely removes all child elements below--wiping all internal state (see the cache data loss).

Interactive demo
Code for this gif -- key-demo.tsx is Root

But what about Keys with lists?

Lists are a special use case for the key prop. This is because React is fairly inefficient with list element rendering. If a list were to be updated with a new element anywhere other than the bottom, React will mutate each item within that list. To prevent this, React uses the key prop within lists to track which element is new and which is not.

For this reason the general wisdom within the community is to never use indices as the key when iterating over a list.

Doing so will confuse the diffing algorithm to what is actually changing.

Take a look below:

list example

Both the left and right list columns are the same data. The only difference is that the left list keys off an index while the right keys off a locally unique value.

Once both inputs are checked, the Add Item button is selected. The buttom adds additional elements to the top of the list.

As a result, the input stays with the index 0 key on the left, but travels with the properly selected a label on the right. The diffing algorithm does not notice the issue on the left whereas the key has not changed!

Interactive demo
Code for this gif

Thanks for reading!

Top comments (0)