Keys
You may have noticed, in the previous lesson, that we get a console warning:
Warning: Each child in a list should have a unique "key" prop.
When we give React an array of elements, we also need to help React out by uniquely identifying each element.
Here's how we can fix this issue in our CRM application:
const data = [ { id: 'sunita-abc123', name: 'Sunita Kumar', job: 'Electrical Engineer', email: 'sunita.kumar@acme.co', }, // ✂️ Other contacts trimmed];function App() { return ( <ul> {data.map(contact => ( <ContactCard key={contact.id} name={contact.name} job={contact.job} email={contact.email} /> ))} </ul> );}
Conveniently, the contacts in our data
array have a unique identifier, id
. We set that string as the key
, and the React warning goes away.
Here's the live sandbox, if you want to play with it for yourself:
Code Playground
The purpose of a key
is to uniquely identify each React element.
Why are keys necessary?
One of my personal pet peeves when I'm learning something is when the instructor tells me what to do, but not why it's necessary or how it works.
It does seem kind of odd, right? Why does React need us to annotate each element with a unique key
? Can't it figure this out on its own??
Let's talk about it.
Video Summary
- In order to truly understand why keys are necessary, we'd need to go way deep into the React renderer. We'll get there later in the course, but for now, we'll stick to a high-level summary of the problem.
- React doesn't actually know specifically what happens when we change data. All React sees is the before/after. React has to figure out how to change the DOM to match this new snapshot.
- There are an infinite number of possibilities, and React doesn't actually know which one to take. It could delete and add new items, or it could edit existing ones.
- Different approaches have different implications from a performance perspective. There are also edge-case bugs that can arise.
- Keys exist to show React the route. By uniquely identifying each item across all snapshots, React can figure out exactly what to do, ensuring the best performance and no edge-case issues.
- Don't worry if this still isn't making a ton of sense. We'll learn more about keys later in the course!
Where does it go?
So, the previous video talked about keys at a very high level, in terms of their purpose and why they're necessary.
There's another little mystery with keys. This one is much lower-level.
Take a look at the following JSX:
const element = ( <ContactCard key="sunita-abc123" name="Sunita Kumar" job="Electrical Engineer" email="sunita.kumar@acme.co" />);
At first glance, it seems like we've given this component 4 props: key
, name
, job
, and email
.
But, if we add a console.log
to this ContactCard
component, we'll notice that something's missing:
function ContactCard({ key, name, job, email }) { console.log(key); // undefined console.log(name); // 'Sunita Kumar' console.log(job); // 'Electrical Engineer' console.log(email); // 'sunita.kumar@acme.co'
return ( <li className="contact-card"> {/* ✂️ Removed for brevity */} </li> );}
We've specified 4 props, but only 3 have come through! key
has not been provided.
Here's the deal: there are a small number of “reserved words” in React. key
is one of these special terms. When we apply a key
to a React element, we're not actually setting it as a prop.
Let's dig into this. First, let's take a look at this in plain JavaScript, no JSX:
const element = React.createElement( ContactCard, { key: 'sunita-abc123', name: 'Sunita Kumar', job: 'Electrical Engineer', email: 'sunita.kumar@acme.co', });
Hmmm… So far, key
still looks like a prop. Let's keep going.
This code shows that we're calling the React.createElement
function. As the name implies, this function creates a React element. If we were to execute this code, we'd be left with something like this:
const element = { type: ContactCard, key: 'sunita-abc123', props: { name: 'Sunita Kumar', job: 'Electrical Engineer', email: 'sunita.kumar@acme.co', }}
Ah-ha! The React.createElement()
function has taken our data and used it to produce a React element, and that element has key
as a top-level property!
As we saw in the “Build Your Own React” lesson, React elements are JavaScript objects that describe a thing that React needs to create. In this case, the element describes a ContactCard
component that needs to be rendered.
Keys identify a particular React element. It's a property of the element itself, not something that needs to be passed along to the component!
We're getting into some pretty advanced territory here. The relationship between elements and components is something we'll be revisiting throughout the course, and I don't want to get too far ahead of myself.
For now, the important thing to understand is this: key
looks like a prop, but it's a special thing that React uses to identify React elements.
Key rules
Let's look at some of the rules that govern how keys should be used.
Top-level element
In order to satisfy this requirement, the key
needs to be applied to the very top-level element within the .map()
call.
For example, this is incorrect:
function NavigationLinks({ links }) { return ( <ul> {links.map(item => ( <li> <a key={item.id} href={item.href} > {item.text} </a> </li> ))} </ul> );}
From React's perspective, it has a group of <li>
React elements, and it doesn't see any unique identifiers on them. It doesn't "dig in" and look for keys on children elements.
Here's how to fix it:
function NavigationLinks({ links }) { return ( <ul> {links.map(item => ( <li key={item.id}> <a href={item.href}> {item.text} </a> </li> ))} </ul> );}
When using fragments, it's sometimes required to switch to the long-form React.Fragment
, so that we can apply the key:
// 🚫 Missing key:function Thing({ data }) { return ( data.map(item => ( <> <p>{item.content}</p> <button>Cancel</button> </> )) );}
// ✅ Fixed!function Thing({ data }) { return ( data.map(item => ( <React.Fragment key={item.id}> <p>{item.content}</p> <button>Cancel</button> </React.Fragment> )) );}
Not global
Many developers believe that keys has to be globally unique, across the entire application, but this is a misconception. Keys only have to be unique within their array.
For example, this is totally valid:
function App() { return ( <ul> {data.map(contact => ( <ContactCard key={contact.id} name={contact.name} job={contact.job} email={contact.email} /> ))} {data.map(contact => ( <ContactCard key={contact.id} name={contact.name} job={contact.job} email={contact.email} /> ))} </ul> );}
Each .map()
call produces a separate array, and so there's no problem. 👍