DEV Community

Cover image for The significance of React keys - a visual explanation
Josh Tonzing
Josh Tonzing

Posted on • Updated on

The significance of React keys - a visual explanation

Disclaimer: This is a gross oversimplification and should be used as a basic guide on how reconciliation works, not a perfect example!

When handling arrays in React, utilisation of the 'key' attribute on each element can be crucial for avoiding needless rerender performance hits. This article will hopefully explain why you should always clearly define your keys, and what you are missing out on if you are not doing so.

Let us start with an array

const joshs = [{  Name: "Josh", }, { Name: "Joshina", }, {  Name: "Notjosh", }]
Enter fullscreen mode Exit fullscreen mode

the business end of our React component which renders said array

<div>
    { joshs.map((person, index) => ( <span key={index}>{person.name}</span>)) }
</div>
Enter fullscreen mode Exit fullscreen mode

and the HTML it outputs

<div>
    <span key=’0’>Josh</span>
    <span key=’1’>Joshina</span>
    <span key=’3’>Notjosh</span>
</div>
Enter fullscreen mode Exit fullscreen mode

Works great!

...but!

A new Josh has arrived, and he's told the others to move out of the way.

 [{ Name: "Mega Josh"}, {  Name: "Josh", }, { Name: "Joshina", }, {  Name: "Notjosh", }]
Enter fullscreen mode Exit fullscreen mode

Our component receives the new list, does its thing...

<div>
    { joshs.map((person, index) => ( <span key={index}>{person.name}</span>)) }
</div>
Enter fullscreen mode Exit fullscreen mode

and prints it out like it did before.

<div>
    <span key=’0’>Mega Josh</span>
    <span key=’1’>Josh</span>
    <span key=’2’>Joshina</span>
    <span key=’3’>Notjosh</span>
</div>
Enter fullscreen mode Exit fullscreen mode

Works great!

...but!

Let us look under the hood on what's actually happening (in a very simplified fashion) with the process that React is going through when it renders its new list.

A React component, when you boil it down to its rawest form (i.e. convert it from JSX), is just an object with a set of properties. These properties define its type, name, state, what props it has received, whether it has children, etc.

Each time a change occurs in our array, a new list of Josh <span> component objects are created. The React reconciler will compare the newly created objects with the current versions it has in the DOM. If any differences are detected between certain properties, it will redraw the components believing that it's the same object, but properties have changed.

So with our example, we have our original array (of components), which can loosely be translated to something like this...

[{
  Type: "span",
  Key: "0",
  Children: "Josh"
}, {
  Type: "span",
  Key: "1",
  Children: "Joshina"
}, {
  Type: "span",
  Key: "2",
  Children: "Notjosh"
}]
Enter fullscreen mode Exit fullscreen mode

The reconciler will look at the key, and the component properties (in our simplified case, the content or children), and then look through its previous list of components to see if it matches any previous combinations.

As our list uses the array index position as its key, when 'Mega Josh' arrives and shifts all the components down one position, every comparison now fails due to React noticing that the keys do not match their previous properties!

[{
  Type: "span",
  Key: "0",                // Expected 0 to match 'Josh'
  Children: "Mega Josh"     // IM DIFFERENT, REDRAW ME
}, {
  Type: "span",
  Key: "1",                // Expected 1 to match 'Joshina'
  Children: "Josh"          // IM DIFFERENT, REDRAW ME
}, {
  Type: "span",
  Key: "2",                // Expected 2 to match 'Notjosh'
  Children: "Joshina"       // IM DIFFERENT, REDRAW ME
}, {
  Type: "span",   
  Key: "3",                // IM NEW
  Children: "Notjosh"       // DRAW ME
}]
Enter fullscreen mode Exit fullscreen mode

But! We can prevent this. If we clearly define a key which is static, unique, and uniquely associated with the properties it is related to, React can acknowledge that it's the same component, even when it has changed its position.

Let us rebuild our components with unique keys

n.b. In this example I use the name attribute to keep our josh objects simple, but this is not best practice as the likelihood of two components having the same key is quite high. Where possible you should always use some sort of primary key from the data object.

<div>
    { people.map((person, index) => ( <span key={`key-${person.name}`}>{person.name}</span>)) }
</div>
Enter fullscreen mode Exit fullscreen mode

the exported HTML will now look like

<div>
    <span key=’key-Josh’>Josh</span>
    <span key=’key-Joshina’>Joshina</span>
    <span key=’key-Notjosh’>Notjosh</span>
</div>
Enter fullscreen mode Exit fullscreen mode

and the updated array HTML

<div>
    <span key='key-Mega Josh'>Josh</span>
    <span key=’key-Josh’>Josh</span>
    <span key=’key-Joshina’>Joshina</span>
    <span key=’key-Notjosh’>Notjosh</span>
</div>
Enter fullscreen mode Exit fullscreen mode

The keys are now unique to their data object (rather than their array position), so when we do our object comparison

[{
  Type: "span",
  Key: "key-Josh",
  Children: "Josh"
}, {
  Type: "span",
  Key: "key-Joshina",
  Children: "Joshina"
}, {
  Type: "span",
  Key: "key-Notjosh",
  Children: "Notjosh"
}]
Enter fullscreen mode Exit fullscreen mode

the reconciler will see that some components haven't changed, they have simply moved, only the new component will be created and inserted at the front of the list, not affecting the components after it. Brilliant!

[{
  Type: "span",
  Key: "key-Mega Josh",    // IM NEW
  Children: "Mega Josh"     // DRAW ME
}, {
  Type: "span",
  Key: "key-Josh",         // Expected 'key-Josh' to match 'Josh'
  Children: "Josh"          // IM THE SAME, DONT REDRAW ME
}, {
  Type: "span",
  Key: "key-Joshina",      // Expected 'key-Joshina' to match 'Joshina'
  Children: "Joshina"       // IM THE SAME, DONT REDRAW ME
}, {
  Type: "span",
  Key: "key-Notjosh",      // Expected 'key-Notjosh' to match 'Notjosh'
  Children: "Notjosh"       // IM THE SAME, DONT REDRAW ME
}]
Enter fullscreen mode Exit fullscreen mode

For some uses, the performance impact may be minimal (or even non-existant if the array never changes order). The benefits with adding keys will be noticeable with very large arrays that get sorted/re-ordered as it will eliminate the need for a majority of your list to be rendered. Magic!

Discussion (8)

Collapse
edmundcwm profile image
edmundcwm

Regarding the opening statement - '...utilisation of the 'key' attribute on each element can be crucial for avoiding needless rerender performance hits.', am I right to say that the 'key' attribute doesn't prevent or reduce the number of times a component is re-rendered? Instead, it's more of making React's life easier when it comes to deciding what change is needed to update the browser DOM?

Love the explanation btw!

Collapse
ashwath26 profile image
Ashwathns

Very nice explanation, finally understood :)

Collapse
stupidlymoron profile image
Khan Irfan • Edited

Typo in 3rd image / Code snippet

<div>
    <span key=’0’>Josh</span>
    <span key=’1’>Joshina</span>
    <span key=’3’>Notjosh</span> (here)
</div>

last span should have key = '2'.

Collapse
niinpatel profile image
Nitin Patel

Great explanation! I now finally understand why keys are needed when rendering component arrays!

Collapse
runyaozhang profile image
RunyaoZhang

Finally understood!!! Thanks so much!

Collapse
efrenshou profile image
Efrén Vázquez Solís

I felt this article like some "explain to me like I'm 5" tutorial so it was very clear for me, thank you for this post.

Collapse
samaid2025 profile image
Samaid2025

Finally. Went through a lot of jibberish on React Keys. This article explains it in the most simple and clear way