Proper use of keys in React lists
Why do we need keys
There is a great tool in React that allows us to render list items.
{data.entries.map((entry) => (
<div>
<p>{entry.name}</p>
<img src={entry.image} />
</div>
))}
But what exactly happens when we add new elements in the middle of the list? We would expect existing elements don’t change since they are the same. A new element is mounted in the middle.
But that’s not what happens. In reality, every element is the same for React. They don’t have names, and the only difference between them is their position on the list. So React goes through the list one element at a time and renders the tree anew. This has the worst performance if the added element is at the start of the list.
That happens because React doesn’t know that Element 2
and Element 3
are the same, and there is no need to rerender them. Luckily for us React developers provided us with the ability to “mark” the elements, that are going to stay the same.
{data.entries.map((entry) => (
<div key={entry.id}> // using 'key' to mark the list entry
<p>{entry.name}</p>
<img src={entry.image} />
</div>
))}
In this example, we assume that entry
has a unique id
property. Then it’s going to work the way we want.
What happens if we use keys that aren’t unique
Considering what we already know about the keys, we can predict what will happen if we provide the keys, but don’t provide the uniqueness. In this case, React can skip the update of an element when it’s needed or duplicate the element.
React warns us if this happens.
What to use for a key
Keys must be unique and consistent (do not change between rerenders)
id
for a key
Best way to go, assuming your data source model has the unique id
. Most APIs do that.
Indexes for a key
This looks like a natural decision, since indexes are easy to reach inside map
, and they are unique.
{data.entries.map((entry, index) => (
<div key={index}>
<p>{entry.name}</p>
<img src={entry.image} />
</div>
))}
Actually, this is how React works under the hood if we don’t provide keys at all, it will use indexes as keys. So explicitly setting indexes as keys is useless.
This will work fine until the list is reordered or new elements are added/deleted. Since indexes are not consistent, it’ll work the same way as if we provide no keys at all.
Worst thing about using indexes as keys is not the bad performance but the lost identity of elements, which may be the source of elusive bugs
For example, if list elements have uncontrolled inputs as children, their state will get mixed up once we reorder the list. This happens because children are tied to their parents, and since we are using indexes, on change it swaps parents. You can check the example provided on React docs.
Another example is when elements get deleted. The picture below shows how elements 3 and 4 are now using the states of elements 2 and 3 respectively. Since the keys didn’t change, react assumes they are the same component.
I modified the example provided above by adding a delete button, so we can reproduce this case.
Considering all the above, we can justify the use of indexes as keys, only for static lists which never get changed or reordered and don’t have id
’s in their data model.
Randomly generated values for a key
It may be tempting to use one of the random uuid generation libraries, like uuid or nanoid.
{data.entries.map((entry) => (
<div key={nanoid()}>
<p>{entry.name}</p>
<img src={entry.image} />
</div>
))}
This way we make sure that on every rerender each list entry will have a different unique key. So React must rerender them all.
We fix the bugs part of using indexes because all tree gets to be rebuilt, but we get the worst performance possible.
When we can use randomly generated values
If we are working with the data stored in our app all the time, or we are not frequently fetching the data from an API instead keeping it inside a context or a store, it’s safe to enrich the data with randomly generated uuid property. That way it’ll become part of the data model and can be safely used in rendering lists.
Another way for data being fetched from external sources is to make id
as a hash, so it would be the same if the data entry contents stay the same.
View keys in DevTools
Keys are not passed into the DOM elements as attributes or into the React components as props. So you won’t see keys inside DevTools.
However, you can see them in the React DevTools. To do this, go into Components -> Settings
and turn off hiding of DOM nodes components.
Comments