I can bet my life that you've seen this warning at least once while building React applications.
Those who say "no" ...
So, back to the problem...
Maybe you thought:
What on earth is a "key"?
Why it should be unique?
And why should I even care, it's just a warning?
So many questions and I hope I will be able to answer all of them.
Let's start with the basics ...
What is a key?
According to the React documentation,
key - is just a special string attribute that must be included when creating arrays of elements.
What did we get from this definition?
Weell, first, we now know that key is of type string and
secondly, we should use it when we work with arrays!
How to use them?
Super simple!
Let's take a look at this code snippet:
const [soccerTeams, setSoccerTeams] = useState([
"Chelsea",
"Real Madrid",
"Liverpool",
"Juventus",
]);
return (
<div className="App">
{soccerTeams.map((team) => (
<p> {team}</p>
))}
</div>
);
As you can see we just loop through the array and display the content of each element.
And this code will produce the warning you've seen in the beginning (that says "each child should have a unique key and blah blah blah")
So, the obvious question is how to get rid of this red little dude with the help of keys, right?
Actually, it's not that hard at all
All you have to do is assign the "key" attribute to array elements that are inside the map() and give it a value of type string.
Probably, your next question could be: what should we provide as a "value" to it?
Hmm, let's think about it ...
As we all know, each element in the array has its own index and this index is always unique.
So why not use it?
Okay, let's try ...
const [soccerTeams, setSoccerTeams] = useState([
"Chelsea",
"Real Madrid",
"Liverpool",
"Juventus",
]);
return (
<div className="App">
{soccerTeams.map((team, index) => (
<p key={index}> {team}</p>
))}
</div>
);
We just added the "key" attribute with the value of index to each array element.
As you can see, it worked!
There is no more this freaking warning!
But perhaps you still ask yourself:
Why did we get this warning in the first place and why does React care so much about it?
Well, have you ever heard anything about reconciliation in React?
I guess, it sounds like something familiar, right?
This post is not about reconciliation and thus we won't spend much time on it but briefly, it is a process that helps React to decide whether Virtual DOM should be updated or not (when component's state changes).
How do keys relate to all this reconciliation stuff?
Basically, when the component re-renders, React compares new keys against the old set of keys and identifies which items have been modified, added, or deleted.
And based on that, updates Virtual DOM.
And that is actually all you have to know about keys in React!
Yeah, dead simple ...
Don't forget to follow me on GitHub and Medium ...
Ookay, okay guys)
Of course, it was just a joke!
Remember, it is React and it just can't be that simple and easy.
There is always a catch!
Let's take a look at this code.
const [soccerTeams, setSoccerTeams] = useState([
"Chelsea",
"Real Madrid",
"Liverpool",
"Juventus",
]);
const deleteItem = (index) => {
const tempArray = [...soccerTeams];
tempArray.splice(index, 1);
setSoccerTeams(tempArray);
};
return (
<div className="App">
{soccerTeams.map((team, index) => (
<div key={index}>
<p> {team}</p>
<button onClick={() => deleteItem(index)}>
Delete from array
</button>
</div>
))}
</div>
);
Everything is almost the same as the previous one - we use "index" as "key", no more warning.
But we added a button so we can delete the particular items from the array.
So, what gonna happen when we delete the item from an array?
Logically, it should just erase this particular node from DOM and that's it, right?
Other items have already been rendered, and why update them
Good logic, but let's see what is really going to happen.
Can you see those purple flashes in dev tools?
They indicate that something has been updated inside the DOM.
And as you can see with your own eyes, those elements that have already been in DOM were updated too.
Why?
Let's go through this process very carefully.
We have an array that consists of 4 elements.
The first element has index 0, the second one - index 1, and so on.
Then we delete the first element.
What happens?
Our second element that had index 1, now has index 0.
And as you already understand, the key is also updated because we assigned it to be equal to the index (that was updated) of our element.
The same nonsense occurs when we add a new element to the array.
Also, I want to bring to your attention the fact, that when we add a new element to the end of the array or delete an item from the end, nothing is gonna happen and everything will work totally fine (DOM won't update other elements)
But that's not always the case.
Therefore, keys always should be stable.
How to make keys constant?
Very good question.
One of the best ways is the usage of ID as a key's value.
Most of the time when we fetch data from some sort of database, items we fetch have their own unique IDs.
And these IDs can be used as keys.
Let me show a quick example of how it can be done
const [soccerTeams, setSoccerTeams] = useState([
{ team: "Chelsea", id: "667" },
{ team: "Liverpool", id: "545" },
{ team: "Juventus", id: "1393" },
{ team: "Real Madrid", id: "432" },
]);
const deleteItem = (index) => {
const tempArray = [...soccerTeams];
tempArray.splice(index, 1);
setSoccerTeams(tempArray);
};
return (
<div className="App">
{soccerTeams.map((element, index) => (
<div key={element.id}>
<p> {element.team} </p>
<button onClick={() => deleteItem(index)}>
Delete from array
</button>
</div>
))}
</div>
);
Now, elements in our fictional database have their own IDs.
When we loop through the array we use this "id" property as a value for the "key" attribute.
But the main question is what's gonna happen when we will delete the element from an array.
Intrigued?
Yo ho ho ...
No more purple flashes !
Only parent div updated as one element was removed in it, but other list items were not re-rendered because we use constant ID as a key.
So even we delete an item, the keys for other elements stay the same as we don't use indexes as their values.
Amazing! Problem solved!
Buut ... what if your data does not have IDs?
Good question, because not all data has IDs.
But what if can generate one?
For example, let's try to use a popular ID generation tool called UUID.
const [soccerTeams, setSoccerTeams] = useState([
{ team: "Chelsea" },
{ team: "Liverpool" },
{ team: "Juventus" },
{ team: "Real Madrid" },
]);
const deleteItem = (index) => {
const tempArray = [...soccerTeams];
tempArray.splice(index, 1);
setSoccerTeams(tempArray);
};
return (
<div className="App">
{soccerTeams.map((element, index) => (
<div key={uuidv4()}>
<p> {element.team} </p>
<button onClick={() => deleteItem(index)}>
Delete from array
</button>
</div>
))}
</div>
);
In this example, we generate value for the key using the UUID() function.
Oh no ...
We are back to our initial problem.
DOM is updated every time we delete the item from an array.
I guess you already understand why.
Every time a component re-renders, a new ID is generated and assigned to the key.
So, React thinks it is a brand new element, while it's not.
But there is another way we can use UUID.
const [soccerTeams, setSoccerTeams] = useState([
{ team: "Chelsea", id: uuidv4() },
{ team: "Liverpool", id: uuidv4() },
{ team: "Juventus", id: uuidv4() },
{ team: "Real Madrid", id: uuidv4() },
]);
const deleteItem = (index) => {
const tempArray = [...soccerTeams];
tempArray.splice(index, 1);
setSoccerTeams(tempArray);
};
return (
<div className="App">
{soccerTeams.map((element, index) => (
<div key={element.id}>
<p> {element.team} </p>
<p> {element.id} </p>
<button onClick={() => deleteItem(index)}>
Delete from array
</button>
</div>
))}
</div>
);
Here we use the UUID() function to generate ID for the id property.
This way everything works fine!
Is there another way?
Actually, yeah.
We can use some hashing tool to generate a hash from the object and use it as a key's value.
const [soccerTeams, setSoccerTeams] = useState([
{ team: "Chelsea" },
{ team: "Liverpool" },
{ team: "Juventus" },
{ team: "Real Madrid" },
]);
const deleteItem = (index) => {
const tempArray = [...soccerTeams];
tempArray.splice(index, 1);
setSoccerTeams(tempArray);
};
return (
<div className="App">
{soccerTeams.map((element, index) => (
<div key={hash(element)}>
<p> {element.team} </p>
<p> {hash(element)} </p>
<button onClick={() => deleteItem(index)}>
Delete from array
</button>
</div>
))}
</div>
);
Here we use the object-hash package, to generate a hash from the object and use it as a key.
As you can see, no problems over here!
But maybe it's not the best approach as hashes don't ensure uniqueness.
Moreover, if you have objects with the same contents, it can lead to a problem!
In the end, let me mention a few things we haven't touched before
Take a look at this code:
const [soccerTeams, setSoccerTeams] = useState([
{ team: "Chelsea", id: "667" },
{ team: "Liverpool", id: "666" },
]);
const [soccerTeams1, setSoccerTeams1] = useState([
{ team: "Juventus", id: "667" },
{ team: "Arsenal", id: "666" },
]);
return (
<div className="App">
{soccerTeams.map((element) => (
<div key={element.id}>
<p> {element.team} </p>
</div>
))}
{soccerTeams1.map((element) => (
<div key={element.id}>
<p> {element.team} </p>
</div>
))}
</div>
);
We have two different arrays.
And as you probably noticed their elements have the same IDs.
Will this cause a problem (like - two children have the same ID and blah blah)?
Nah! Keys could be the same for 2 different arrays
Just remember, keys must be only unique among siblings.
One more example and we are done, guys!
What's wrong with this code?
<div className="App">
{soccerTeams.map((element) => (
<div>
<p key={element.id}>{element.team}</p>
</div>
))}
</div>
We have a key, but in our console, we see the warning.
React is not happy!
The problem is simple - the key needs to go on the outermost returned element!
In our case - on the div element.
<div className="App">
{soccerTeams.map((element) => (
<div key={element.id}>
<p>{element.team}</p>
</div>
))}
</div>
Hah! Problem solved!
Is there any default value?
Almost forgot) Yeah, there is.
According to React docs, if you choose not to assign an explicit key to list items then React will default to using indexes as keys.
But as we already went through this, you understand that maybe it's not a good idea!
Brief conclusion:
Keys are crucially important and you should not neglect using them.
In the best scenario, you should use IDs as values for keys, but if you do not have ones, you can use hashing, ID generation tools.
In some cases, it's totally appropriate to use indexes (if it's just simple data that won't change in the future)
And that's it, guys.
I hope that you have learned something new today!
I would appreciate it if you could like this post or leave a comment below!
Also, feel free to follow me on GitHub and Medium!
Adios, mi amigos)
Top comments (0)