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 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:
{id: 'apple', value: '🍎 apple'},
{id: 'orange', value: '🍊 orange'},
{id: 'grape', value: '🍇 grape'},
{id: 'pear', value: '🍐 pear'},
And the solution is pretty simple:
+ <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:
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):
{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 li
s 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:
{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:
{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
li
s 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 key
s this time:
{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:
{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 😄