ReactJS: Why Index as a key is an anti-pattern

Vladimir Topolev
Geek Culture
Published in
5 min readJul 27, 2021

Let’s try to understand why it’s important and everybody needs to take some time to deeply understand how React leverages with the component key under the hood and the main idea around this concept.

What do the purpose of a key in React?

Let’s recall, during any state changes, React builds a new state of the Virtual DOM tree and compares it with the previous one, and defines an optimal bunch of operations that should be applied to the real browser DOM (figure 1) for a new transition. React compares Virtual DOMs with a special Reconciliation algorithm.

Figure 1 — React workflow

A model (figure 1) is a tree of React elements, and each React element has a type. It may be String, React Component or React function. Example of React Model provided in Figure 2.

Figure 2 — React Elements

I intentionally used Pure JS instead of JSX syntax, but the same model with JSX syntax looks like that (figure 3):

Figure 3 — React model with Pure JS and JSX syntax

The reconciliation algorithm compares each React Elements basing on its type. Difficulties appear when we need to render a list of N homogeneous React elements, in our case we render two React Elements with type Item.

Let’s imagine, that during application initialization we need to show three items. The user is deleting the first item, with index 0 (figure 4).

Figure 4 — Transition between states after deleting the first item in the list

Since all React elements have the same type, React couldn’t understand our intentions. In this case, it would compare each item one by one (figure 5).

Figure 5 — Defining DOM operations for transition

Here we see, that React defines 3 manipulations against real DOM. Could we consider this as an optimized one? Definitely no. In this case, there’s only one operation enough — we need just delete the first node from DOM. Here keys come in place.

Let’s associate each Item with a unique and stable key. Usually, it’s an ID of an item from DB. In this case, keys help React identify which items have changed, are added, or are removed (figure 6).

Figure 6 — An optimized bunch of DOM manipulation for transition

When we help React to match the exact position for each component in an array from the previous state into the current state based on a unique key, it may calculate a more optimized bunch of DOM operations. In figure 6 you see — that react instead of 3 manipulations (figure 5) need to complete only DOM operation. Also, let’s have a look at figure 5 and ask yourself, how many operations React would complete if a list contains millions of items and the user is deleting the first item in the list?

Broken functionality

In the section above, we explained that key may help React make DOM updates more efficient. But wrong key definitions may lead to application bugs.

Let’s develop an application, where we need to render a list of Users. Each User item by default collapsed, if you would like to get more details, you just need to click on it (figure 7). Moreover, the application allows deletion of User items as well.

FIgure 7 — Application functionality

Nothing difficult here:

Pay your attention, that here we define item key as an array index (line 20).

Each User component has an internal state which allows changing state from collapsed to opened.

To play a little bit I’ve prepared codesandbox for you: https://codesandbox.io/s/react-key-as-array-index-kn58z

But here we introduced a bug. The process to reproduce it is provided in figure 8. Let’s assume that we have 3 collapsed Users. We would like to see specific details of the second User (Marry), we click the collapse button. Let’s then delete the second opened item. As you may see, we don’t see Marry item anymore, but Peter is opened, despite that, we didn’t open this item before.

Figure 8 — Steps to reproduce the bug
Figure 9 — Steps to reproduce the bug

Why has it happened? You’ve already seen this case (figure 5). Since we associated each component with an array index, react just compares each node one by one based on its index. In the previous state, the second place (index 1) takes the User component with props Marry and we toggled the internal state from false to true. When we delete Marry item, the second place takes the User component but with new props Peter (figure 9). Since the new state has only 2 elements, React removes the third User element at all.

Figure 10 — Transition between state, when keys assign as an array index

To fix this, instead of using the key as an array index, we need to use user-id instead (line 6):

React then builds the following list of updates:

Figure 10 — Transition between states, when keys assign as unique and stable values
Figure 11 — Get rid of bug when used for ket unique and stable value

Don’t ignore warning messages in Dev mode

Whenever you forget to put the key for a list of homogeneous components, React warns you about it:

P.S. Well, actually to use IDs as a key, it’s not necessary everywhere. Usage of index array is ok, if you are developing a list of items that isn’t supposed to be changed from one state to another. For example, selector for filter where all items are defined and never changed during user interactions. But if list of items is supposed to have features of deleting, resorting, adding new items— in this case, you have to take care of the key in a proper way to avoid any performance issues and avoid unexpected bugs.

--

--

Vladimir Topolev
Geek Culture

Addicted Fullstack JS engineer. Love ReactJS and everything related to animation