Demystifying React Keys

Why using an index is sometimes not enough


Warning: Each child in a list should have a unique "key" prop.

If you're familiar with React, you've probably come across this warning before. It occurs when attempting to render a list dynamically without providing a key to each list item.

So, provide a key, and the warning disappears. Problem solved? Not always.

In order to fully understand why, it's import to first understand React'sĀ Reconciliation process.

Reconciliation

Reconciliation is the term used to describe the process in which React updates the browser DOM whenever state or props change.

In this process, React will re-render, and then compare the previous component tree to the current component tree (known as diffing) to determine the changes that need to be applied to the browser DOM.

While diffing, React uses element (or component) type and order to determine whether an element needs to be re-created. If the element type and order does not match (and there is no key), the element will be re-created.

Why are keys required?

Without keys, React can't track the position of an element from one render to the next.

Elements changing position is most common with lists - user applies a filter, list order changes.

React would be forced to re-create all list items on each render, which is not only computationally expensive, but would also reset any underlying state.

Take this example of a list that you are asking React to render using the map function:


<li>šŸž Bread. Stock: 5</li>
<li>šŸ„š Eggs. Stock: 3</li>
<li>šŸ„› Milk. Stock: 4</li>

Now, let's pretend the user deletes the first element, šŸž.


<li>šŸ„š Eggs. Stock: 3</li>
<li>šŸ„› Milk. Stock: 4</li>

We know that šŸž was simply deleted and the rest shifted up. But React does not. It cannot know whether šŸž was deleted, or whether šŸ„š was deleted and šŸž renamed to šŸ„š, or whether šŸ„› was swapped with šŸž, šŸ„› renamed to šŸ„š and šŸ„š renamed to šŸ„›.

This might not seem like a big deal, but in a real application, each of these list items would likely have some underlying state, and making the wrong assumption could lead to unintended side-effects.

Enter the key prop

The key prop allows the developer to give React a hint as to which list item is which.

Using the same example, but with a key:


<li key="bread">šŸž Bread. Stock: 5</li>
<li key="eggs">šŸ„š Eggs. Stock: 3</li>
<li key="milk">šŸ„› Milk. Stock: 4</li>

Let's say the user deletes the šŸ„š this time.


<li key="bread">šŸž Bread. Stock: 5</li>
<li key="milk">šŸ„› Milk. Stock: 4</li>

React knows the second element was deleted, because the key is now gone. As a result React can perform the re-render much more efficiently.

If you don't provide a key, React will take the index by default.

Why using an index is sometimes not enough

Using an index as a key will suppress Reacts warning, however, it can produce unintended side-effects if the list can change (re-order, add or remove elements).

Using the same example, with an index as a key:


<li key={0}>šŸž Bread. Stock: 5</li>
<li key={1}>šŸ„š Eggs. Stock: 3</li>
<li key={2}>šŸ„› Milk. Stock: 4</li>

User deletes the second element, šŸ„š.


<li key={0}>šŸž Bread. Stock: 5</li>
<li key={1}>šŸ„š Eggs. Stock: 3</li>

When šŸ„š was deleted, šŸ„› took over position 2 (index 1) in the array, and therefore key 1. In other words, React thinks šŸ„› was deleted because key 2 no longer exists.

This funky behaviour can depend on your implementation, but generally it's best not to mess around and simply provide React a key that meets the following criteria:

  • It must be stable (not change during the lifetime of the component)
  • It must be unique within the list

Try it yourself

Here is a simple stock management app. It displays a list of products and the number of stock available for each one. It has two main features:

  • The user can update the stock for a particular product
  • The user can shuffle the order of the products

Unfortunately, the developer used the product index as the key for each <Product />. As a result, shuffling doesn't work as expected. Can you fix it?

Hint: you'll need to find (or create) a unique identifier for each product, and assign that as the <Product /> key.