Keys in react

Today we’ll talk about thekey prop in react.
Frontend developers who just started using react usually don’t pay a lot of attention to thekeyprop.

Here is the plan of the article:

  1. Reconciliation and keys
  2. Reusing key and normalization
  3. Using key to render only one element
  4. Working with key in children

There’s lots of material, so each section has intermediate conclusions.


Reconciliation

React key main goal is to help the reconciliation mechanism.

Let’s create a small component which renders list of names:

We didn’t use the key prop. Consequently, we’ll see in the console such message:

Warning: Each child in an array or iterator should have a unique “key” prop.

Let’s solve a more difficult task. We’ll implement an input with buttons to add a new name either to the beginning or to the end of the list. Besides it, componentDidUpdate and DidMount lifecycle methods will log changes specifying children:

Try to add “Richard” to the end, and then “Max” to the beginning. Pay attention to the console.
Video demo

When we add element to the beginning, our Name components will be rerendered. The new component with children === Richard will be created.

Updated from Lindsey to Max
Updated from Mark to Lindsey
Updated from Tom to Mark
Updated from Richard to Tom
Mounted with Richard

How does it work? Let’s study the reconciliation mechanism.


Render function creates a react-components tree. When the state or props updates, render function will return another react-components tree. React has to compare actual and new trees and apply changes. Such trees comparison algorithms have a complexity in the order of O(n³). It means, if you have lots of elements (for example more than 1000) “diffing” algorithms will be too slow. For this reason VDOM reconciliation mechanism implements heuristic O(n) algorithm using two assumptions (rules):

  1. Two elements with different types produce different trees. Consequently, as soon as you change element type from a div to a section or any other tag, react will decide trees in the div and the section as different. Even if you change only the tag. React will remove tree from the div and mount all elements into the section. It also means, if you change one react component to another the situation will be the same (trees will be resolved as different), even their content is similar.

Same situation will be with react components:

2. When react compares elements lists, it just iterates through both of them and generates mutations. So in our example, we were got rerender of the whole list. Let’s recognize this work in the example:

Foremost react checks <li>Tom</li> with another one, then <li>Max</li> and in the end it detects, that <li>Lindsey</li> isn‘t presented in the old tree. So react creates such element.

In the case when we added an element to the beginning:

In this example, react compares <li>Tom</li> with<li>Lindsey</li> and updates the element. Then react compares <li>Max</li> with <li>Tom</li> and updates it. In the end react mounts a new element <li>Max</li>. So it means, when you add an element to the beginning, react will update each element in the list.

Key prop allows you to solve this issue. When adding a key, react won’t compare elements one by one. It will search by the key value. Our example will be more effective, when we use keys:

React finds elements with key='1', key='2', recognizes these elements don’t have any changes, and then react mounts a new element <li key=’3'>Lindsey</li>. Therefore, react mounts only one component, when you use keys.

Now we’ll rewrite the example using keys:

Pay attention, when you add element to the beginning of the list, react will change\create only one component

We’ve added the id field to the names model and set keys value to the value of the id field. It’s good practice not to use array indexes as a key value because every element will change its index if we add a new element to the beginning of the list.

Let’s sum up the first part:

Keys allow you to optimize work with lists of elements. They decrease the number of redundant DOM mutations.


Reusing key and normalization

Let’s make a task a little harder. Now we create list of persons, who are developers on several teams. Let’s assume there are 2 developer teams employed in the company. You can select each person by click in those teams. Our first solution could be:

Try to select people from different teams and switch between those teams.

We have a side-effect. When we select person and switch the team, there will be a select/unselect animation, even if person was never selected. You can see such example in video:

When we reuse the keys where it isn’t needed, we’ll get side-effects, because react will update elements instead of removing and creating new components.

It happens so, as we use the same keys for different persons. Therefore, react reuses elements even if it isn’t needed. Moreover, if you want to add new persons, you’ll have to write a pretty complicated code.

There are some problems in the code above:

  1. Data isn’t normalized. We have to do some additional operations.
  2. There are duplicated keys in the developer entity. Therefore, react doesn‘t recreate the component, but just updates it. This leads to side-effects.

There are two ways to solve this problem. The simplest solution is to create complex key for developer entity, which format will be `${team id}.${developer id}` . keys won’t be duplicated in this way. We’ll get rid of side-effects.

We also can normalize our data. In the component state, we can store the two fields: teams and developers. Developers field will store map with pairs id: name, and teams field will have the list of developers ids describing team members. Let’s implement this solution:

All elements are being processed correctly:

Data normalization simplifies interaction between data and view-layer. Also it simplifies structures complexity. For example, compare toggle function with normalized and denormalized data.
Hint: if backend or API sends only denormalized data, you can normalize them using https://github.com/paularmstrong/normalizr

Let’s sum up the results of second section:

When you use keys it’s important to understand, that if you receive a new entity, key will have to be changed. And key shouldn’t be changed if you update existing entity. 
The most frequent mistake I see is using an array element index as a key. This leads to side-effects.

Data normalization and\or complex keys allow you to get correct code behavior:

  1. When entity mutates, element with the same key will be updated.
  2. If element with the key doesn’t exist anymore, instance with such key will be removed.
  3. New instances will be created with the new keys without duplication.

Using key to render only one element

We have already discussed that react compares elements one by one if there is no key. If key exists, react will search an element with the same key. If children field has only one element with the key prop, react will also try to find element by key.

Now we are going to add notifications to our application. Let’s assume only one notification can be displayed simultaneously. After several seconds notification should be hidden.

Such notification can be implemented pretty easily. We’ll create a component that has the componentDidMount method which sets up a timeout. When the timeout is triggered component will be hidden with animation. For example:

This component doesn’t have callbacks like onClose, but it isn’t important in our demo.

We have created the pretty simple component. Unfortunately, the component has a bug. Suppose a user is pressing the button repeatedly for a long time. The timeout would count 3 seconds after the first click. When the timeout calls the callback, our notification will receive notification_hide css-class and will become “invisible” for the user (as we didn’t use key). As a rule, the most frequent solution is using react lifecycle-methods to correctly reset timeout and so on:

We wrote a bit more code, which will reset timeout and remove notification_hide class when notification content changes. This solution is a good practice.

On the other hand we can solve this task, using key prop. Let each notification has its own unique id. We are going to use this id as a key prop. If we change the notification key, theNotification will be recreated. The component will work as we expect.

Suchwise

In a few cases, we can use the key prop when we render the only one component. The key prop is a very useful way to help the reconciliation mechanism to decide if it should update or (re)create an element.


Working with key in children

One of the key features is that you can’t get access to the key prop from child component. It works so, because key is a special prop. There are only two special props in react: key and ref .

Moreover, we receive a warning in the console:

Warning: TestKey: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://fb.me/react-special-props)

If a component receives children which have the key props you will have access to that children keys and you can process their key prop, but the key will be in the component properties, instead of the prop object:

We can see the following in the console output:

1
prop1
2
prop2
3
prop3
10
prop10

In the end

Today we paid our attention to the key prop in react. We have had a look at the reconciliation mechanism. We worked with and without the key prop and used the key for the only one child. Let’s sum up:

  1. If you don’t set up keys, the reconciliation mechanism will compare components in pairs between actual and new VDOM. So, it’s possible to produce a lot of unnecessary changes.
  2. When you add a key, reconciliation will search component or element with the same key (tag or component name is important). Consequently, you optimize the number of DOM updates.
  3. Make sure that there are no duplicating keys in your lists. This can lead to undesirable side-effects, such as unnecessary animations or an incorrect behavior of the element.
  4. In rare cases, the key may be specified for the single element. This reduces the size of the code and makes it easier to understand. But the scope of this approach is limited.
  5. key and ref are special props in react. They can’t be accessed through the prop object and they are inaccessible from the component instance. We can access child.key or child.ref from the parent component which receives the children prop, but it’s a pretty bad practice. If you need key value in a component, just duplicate it in id prop.