Articles

Why React needs a key prop

Kent C. Dodds
Kent C. Dodds

If you've used React for a while, you've probably run into this before:


index.js:1 Warning: Each child in a list should have a unique "key" prop.
Check the render method of `App`. See https://reactjs.org/link/warning-keys for more information.
at li
at App (http://localhost:3000/static/js/30.chunk.js:48:75)

You probably know the solution as well. Here's the rule:

Whenever you're rendering an array of React elements, each one must have a unique key prop.

Here's the simplest way to reproduce this problem:


const items = [
{id: 'apple', value: '🍎 apple'},
{id: 'orange', value: '🍊 orange'},
{id: 'grape', value: '🍇 grape'},
{id: 'pear', value: '🍐 pear'},
]
function App() {
return (
<ul>
{items.map((item) => (
<li>{item.value}</li>
))}
</ul>
)
}

And the solution is pretty simple:


- <li>{item.value}</li>
+ <li key={item.id}>{item.value}</li>

But like... Why? React doesn't seem to have a problem if I were to write it like this:


function App() {
return (
<ul>
<li>🍎 apple</li>
<li>🍊 orange</li>
<li>🍇 grape</li>
<li>🍐 pear</li>
</ul>
)
}

It's only a problem if I try to pass the elements as an array. So yeah, why does React have such a big problem with arrays anyway?

First, if you want to understand what problems can happen without the key prop (and cool things you can do with it), read Understanding React's key prop. In this post, I just want to explain why React can't just magically make things work without a key prop.

I want you to pretend you're React and I'm a React developer building an application. I'm writing a function component called App. You don't get to see my implementation, all you get is the function which you can pass arguments to and receive a return value from.

So let's say I give you that function and tell you to render that to the page. So you call App and you get the following React elements back (learn more about React elements):


const element = {
type: 'ul',
key: null,
props: {
children: [
{type: 'li', key: null, props: {children: '🍎 apple'}},
{type: 'li', key: null, props: {children: '🍊 orange'}},
{type: 'li', key: null, props: {children: '🍇 grape'}},
{type: 'li', key: null, props: {children: '🍐 pear'}},
],
},
}

Ok, first, you're going to warn right? Because the key is null for all of those children. 😉 But really, it's fine. You're React and you know how to take these elements and render them to the page. So you render the ul and lis to the page. All's good.

Ok, so now my App calls a state updater function and so you know you need to re-render. So you call App again and this time you get some new React elements:


const element = {
type: 'ul',
key: null,
props: {
children: [
{type: 'li', key: null, props: {children: '🍎 apple'}},
{type: 'li', key: null, props: {children: '🍊 orange'}},
{type: 'li', key: null, props: {children: '🍐 pear'}},
],
},
}

Ok, so you do a quick and easy diff here:


- {type: 'li', key: null, props: {children: '🍇 grape'}},

Oh, cool, so you can just remove the li from the page right? Yeah, go ahead and just unmount it. Sure, do that. Easy-peasy.

Ooh! I need another state update, so you get the next set of React elements:


const element = {
type: 'ul',
key: null,
props: {
children: [
{type: 'li', key: null, props: {children: '🍊 apple'}},
{type: 'li', key: null, props: {children: '🍐 pear'}},
],
},
}

Now do another diff:


- {type: 'li', key: null, props: {children: '🍎 apple'}},
- {type: 'li', key: null, props: {children: '🍊 orange'}},
+ {type: 'li', key: null, props: {children: '🍊 apple'}},

Hmm... Ok, so now... Should remove the 🍎 apple and 🍊 orange lis and create a new one with 🍊 apple? How certain are you that this is what I meant to do? Maybe what I actually want is to remove the 🍎 apple and rename 🍊 orange to 🍊 apple... Or maybe I want to remove the 🍊 orange and rename 🍎 apple to 🍊 apple. Or maybe I want to remove 🍐 pear, then rename 🍊 orange to 🍐 pear, and then rename 🍎 apple to 🍊 apple...

Now you're thinking "Ugh. What the heck? Mr. Dodds. Give me a break."

So, as React, you do your best guess: You pretend the key is the index. I mean, you gotta do something right? This is what React actually does.

Interestingly, this means that rather than just unmounting the 🍇 grape li on that first state update, you actually unmounted 🍐 pear and updated 🍇 grape to say 🍐 pear 😂. But in a real app, this is no laughing matter. Unmounting and mounting components has implications on the state and side-effects of a component (which I describe in more detail in the aforementioned blog post).

And that also means with this last state update, you unmounted 🍐 pear again and updated 🍎 apple to be 🍊 apple and 🍊 orange got updated to say 🍐 pear. What a disaster!

This is definitely not the most optimal, but what are you gonna do? You have no idea what I meant with that element change!

(By the way, I used this code to validate what was going on, if you want to poke around).

In walks the key prop...

Phew... Wouldn't it be nice if I had given you some way to track the React elements from one render to the next so your diff could be more informative. That way you'd know my intent much better right?

Yeah, let's try that last one, only with keys this time:


const element = {
type: 'ul',
key: null,
props: {
children: [
{type: 'li', key: 'apple', props: {children: '🍎 apple'}},
{type: 'li', key: 'orange', props: {children: '🍊 orange'}},
{type: 'li', key: 'pear', props: {children: '🍐 pear'}},
],
},
}

Ok, nice, so now here comes the update for you:


const element = {
type: 'ul',
key: null,
props: {
children: [
{type: 'li', key: 'apple', props: {children: '🍊 apple'}},
{type: 'li', key: 'pear', props: {children: '🍐 pear'}},
],
},
}

Ha! Now you know exactly what to do! You can now do a much more granular diff. First you notice that the element with the key of 'orange' is gone, so you know you can remove that from the page. And the element with the key of 'apple' has new children so you can simply update it and move on with life without worrying about whether you're doing the right thing.

Oh, and for our very first change when we removed the grape li, you can correctly just remove that one now too.

Conclusion

Now that you've put yourself in React's shoes, hopefully you understand why React can't just magically know what you're doing with your elements and why you need to provide a key prop. Frankly, this is a limitation in React. It would be nice to not have this limitation, but not at the expense of React's simplicity (which is what I love most about React). I hope this article helped you get an solid understanding of why the key prop is needed when rendering arrays.

I'll leave you with one more thought... With your understanding of the key prop now, can you think of situations where you could use a key prop outside an array? The key prop has less to do with arrays, and more to do with controlling when a component is reused or disposed of and created anew. Give that some thought 😄

Get my free 7-part email course on React!

Delivered straight to your inbox.