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 thekey
prop.
Here is the plan of the article:
- Reconciliation and keys
- Reusing
key
and normalization - Using
key
to render only one element - 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
:
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):
- Two elements with different types produce different trees. Consequently, as soon as you change element type from a
div
to asection
or any other tag, react will decide trees in thediv
and thesection
as different. Even if you change only the tag. React will remove tree from thediv
and mount all elements into thesection
. 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:
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:
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:
- Data isn’t normalized. We have to do some additional operations.
- There are duplicated
keys
in thedeveloper
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:
- When entity mutates, element with the same
key
will be updated. - If element with the
key
doesn’t exist anymore, instance with suchkey
will be removed. - 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:
- 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. - When you add a
key
, reconciliation will search component or element with the samekey
(tag or component name is important). Consequently, you optimize the number of DOM updates. - 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.
- 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. key
andref
are specialprops
in react. They can’t be accessed through theprop
object and they are inaccessible from the component instance. We can accesschild.key
orchild.ref
from the parent component which receives thechildren
prop
, but it’s a pretty bad practice. If you needkey
value in a component, just duplicate it inid
prop
.