Understand the Importance of React’s “Key” Prop
Prologue
For those who have written React code before, I am sure you have encountered this infamous warning:
And how would you fix it?
You simply add the key
prop into the tag. But have you wondered why is it so important? I remember my first intuition was that having a “key” would be easier for React to identify each element of the list that are returned, especially if you have a list of things to list out, like from <li>
.
Another way thinking of this situation is like each marathon participant is given a number, so it is easier for the judges to spot each unique runner, instead of remembering “Oh that guy whose name is uh… what’s his name again???”
But, you know what, I feel the need to explain this in a more technical way if I am asked in an interview, so I decided to dive into the topic.
The Importance of the “Key”
Prop
Here’s what the official React documentation’s explanation on the need of “Keys”:
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity.
It’s quite often to see the key
prop when we are iterating an array with methods like map
. Even though we have the definition and knowing when we should use it, how is that still important? Turns out there’s a process called “reconciliation” in React.
The fact that React is one of the most popular framework is because of its fast performance. It is easier to use React to update components and React would use its “diffing” algorithm to run our website/application. That’s pretty much what “reconciliation” means in React. In order to understand the importance of the key
prop, I will go through this step by step.
Without further ado, LET’S GO~
🌴 Step 1: Understand what virtual DOM is 🌴
One of the major features of React is using virtual DOM, instead of real DOM.
(In case you forget, DOM — “Document Object Model” is represented as a tree data structure.)
One of the advantages that virtual DOM (vDOM) has over real DOM is that it is so much faster to update the DOM object and its properties and takes up less memory. When there are new updates/changes, real DOM would re-render all its elements, including the updated element and its children to update the application UI.
On the other hand, virtual DOM would make a copy of the “actual” DOM and compare the difference, which is called “diffing” (I like the fact that they pretty much shorten the term 😂 it’s like saying “sesh” instead of “session”). React would then use the copy to determine what needs to be updated. Then React would re-render and batch the new update onto the actual DOM.
Here’s visual representation (All graphs are credited to javatpoint.com):
In comparison, the cost of virtual DOM is way less expensive, since it does not require to re-render ALL the elements. And that makes React super awesome.
🍉 Step 2: Understand how React iterates the children of a DOM node 🍉
Now we understand how virtual DOM works, it’s time to dive into how React iterates the children of a DOM node. During the process of re-rendering in React using virtual DOM, React iterates over both lists of children at the same time and generates a mutation whenever there’s a difference.
Imagine we have this following React code:
Let’s try to get into React’s “mind”. This is what our original tree is going to look like when we first implement this:
<ul>
<li>React</li>
<li>Angular</li>
</ul>
And now we update our array with an additional item:
<ul>
<li>React</li>
<li>Angular</li>
<li>Node</li> <- the new item
</ul>
It’s easy for React to match the two lists when the two lists are almost identical and only have slight change at the end of the iteration. After diffing, React found the new change <li>Node</li>
and insert that onto the tree.
But what happens when we naively added the new element at the beginning, like this:
<ul>
<li>Node</li> <- the new item
<li>React</li>
<li>Angular</li>
</ul>
It will take React a while to realize that the only change that has been made was the <li>Node</li>
after “mutating” everything and iterated the whole list — in React doc’s words, “can keep <li>Javascript</li>
and <li>React</li>
subtrees intact”.
This “realization” is very inefficient and that’s when the key
prop come into place.
🔑 Step 3: Time to use “key” prop 🔑
With the help of the key
attribute, it helps React to quickly identify the children in the original tree with children in the subsequent tree. Using our example, the key
attribute can be added like this,
With our original tree:
<ul>
<li key="react">React</li>
<li key="angular">Angular</li>
</ul>
Now with our updated data in our subsequent tree:
<ul>
<li key="node">Node</li> <- the new item
<li key="react">React</li>
<li key="angular">Angular</li>
</ul>
With the help of the key
, React now knows key="node"
is their new friend, sorry I mean element in the list and all it has to do is to move the other two OEs (original elements… you know OG? OE…) and no need to mutate.
This is for the purpose of examples. Otherwise in practice, there is usually already a unique ID given from the data, so we can explicitly use the unique ID as the key
.
For instance, with the data that looks like this:
We can make use of the id
and put into our map
function
const listJSFrameworks = frameworks.map(framework =>
<li key={framework.id}>{framework.name}</li>
);
Sometimes that may not be the case and that’s okay. You can find some other ways to generate a key
, but it has to be unique among its siblings (like within the <li>
inside the <ul>
, and not globally unique).
🐧 NOTE 🐧
As a last resort — as in if we don’t have a stable IDs for rendered items, we can use index
as a key
For instance,
const listJSFrameworks = frameworks.map((framework, index) =>
<li key={index}>{framework.name}</li>
);
The React doc stated that the index
key is not recommended when the orders of items may change. It will negatively impact the performance and may causes issues with component state. To prevent that, a React developer (whose article is mentioned at the React doc #GOAL) suggested importing nanoid as a way to create unique IDs and the code could look like the following:
import { nanoid } from 'nanoid';const createNewTodo = (text) => ({
completed: false,
id: nanoid(), // this part
text
}
Here’s the article (Index as a key is an anti-pattern) in case you are interested.
🎉 YOU MADE IT! 🎉
Some people prefer the top down learning method while I prefer learning from bottom up — understanding the root first and then spread to the branches. I hope the way I understand this could also help you understand what’s really going on. :)
Before I bid you adieu, let’s conclude our 3-step to the importance of key
:
Step 1: Understand the virtual DOM
- Our takeaway: understand how React renders its DOM
Step 2: Understand how React iterates the children of a DOM node
- Our takeaway: understand how React mutates the difference with virtual DOM
Step 3: Time to use “key” prop
- Our takeaway: the key of the article (pun intended), a.k.a. the
key
attribute plays an important role to help React identify what needs to be updated and what need not.
Looks like we have come to an end 😢 As always, thanks for being a wonderful reader and hope you had fun reading!