Use the key prop when Rendering a List with React

Kent C. Dodds
InstructorKent C. Dodds

Share this video with your friends

Send Tweet
Published 5 years ago
Updated 2 years ago

JSX is merely JavaScript and to render a list you can use the array method .map to map an array to React elements. However, if you don't use the key prop correctly, it can lead to unexpected results, so we explore what can happen and how to use the key prop correctly.

Instructor: [00:00] Here, we have an app component with an all-items property. We're going to render those out with app.all-items.map, with item. Inside of here, we'll render a div with item.value, and close off that div. We get apple, orange, grape, and pear.

[00:21] If we open up the developer tools, we're going to get this warning from React, saying each child in an array or iterator should have a unique key prop.

[00:30] The problem is that what this expression evaluates to is an array. As we re-render and re-render, React needs to be able to check the old array from the new array, to know which items are removed, which items are added to. To do this, it needs to keep track using a key.

[00:48] For this key, we need to keep it as something totally unique to that item. Lucky us, that's the ID. We can say item.ID, and we're not getting that warning anymore. Now, as things are added and removed as state changes, React can keep track of these nodes.

[01:05] It doesn't actually really matter all that much in this scenario. We're going to add a little bit of code here to demonstrate a scenario where it really does matter.

[01:14] What I added was some state to keep track of some items, because we're going to make that dynamic. We can now add and remove items from that state, and then we render the items based on that state.

[01:25] We have this add button, where we can click to add items, and then we render all the items that are in the state, with a button to remove them and what the value of that item is, and then this input to demonstrate the real problem here.

[01:38] We're going to go ahead and click add. We're going to get that warning, because we don't have a key prop. I'll just add all of the items we have available.

[01:45] Let's go ahead, and I'm going to say 1, 2, 3, and 4. Let's say I want to get rid of the pear, 4. That works great. Now, let's say I want to get rid of orange. That has 2 in it, and I click minus next to the orange. The grape now actually has 2, when it had 3 before. If I do the same here to apple, now I have grape, and that has 1. Things are really messed up.

[02:15] The reason is that this input is not actually associated to these items directly, because we haven't shown React how to keep track of these items and the associated elements. That's why we need to have a key here, so that React can keep track of the elements that are associated with our data.

[02:35] Here, we could try to do an index. Sometimes, that'll work OK, but in our scenario, that won't work. If we have 1, and 2, and 3, and we'll remove one, now things are all kinds of messed up.

[02:50] Sometimes, you'll be forced to use an index. You don't have anything else that's unique. Sometimes, if you just have an array of strings, then you can use the identity of that item itself. In our case, we're lucky, because we have an ID. That's normally what you want to do, is have your data have an ID, a unique identifier.

[03:08] Now, if we add all these, we can say 1, 2, 3, and 4. If we remove 2, then that's going to actually remove 2. We remove 4, we remove 1, and we're left with grape is 3. There's one other scenario where not using a key properly can cause real problems, and that's with element focus.

[03:27] I wrote this demo here for you. Every second, these items are being randomized and then re-rendered. This is an example of not using a key at all. We can see that's being rendered right here, with just an input for each item. If we use the key as the index, with the input for each item, and then we use a proper key, with a unique identifier for each item.

[03:50] If I click in here, you'll notice my focus is staying in the same input. It's not following the item that it should be. If I highlight the text, every single time it's updated, that isn't going to get un-highlighted.

[04:03] We actually have the exact same problem when we use key as an index, because it's not a proper identifier. Only when we use the key properly will our focus be taken to the right place, keeping track with the input that is associated with the item that we are rendering.

[04:18] Even if I select all the text, or if I move anywhere in that input, React will make sure that the user's focus and their selection remains in the right place, because we have the key properly set.

RentPath User 5
RentPath User 5
~ 5 years ago

Hello,

I'm having problems with this example not recognizing the id portion of the item

{items: Array(6)} Inline Babel script:65 Uncaught TypeError: Cannot read property 'id' of undefined at <anonymous>:93:34 at Array.map (<anonymous>) at App.render (<anonymous>:90:23) at finishClassComponent (react-dom.development.js:11360) at updateClassComponent (react-dom.development.js:11337)

<strong>I cut and pasted the example to get going!</strong>

RentPath User 5
RentPath User 5
~ 5 years ago

and the code:

        render(){
            const {items}= this.state
            return(
                <div>
                    <button
                        onClick={this.addItem}
                        >
                        +
                    </button>
                    {items.map((i, index) => (
                        <div key={i.id}>
                            <button
                            onClick={() => this.removeItem(i)}
                            >
                            -
                            </button>
                            {i.value}:
                            <input />
                        </div>
                    ))}
                </div>
                  )
        }
    }
RentPath User 5
RentPath User 5
~ 5 years ago

and the code:

<quote> render(){ const {items}= this.state return( <div> <button onClick={this.addItem} > + </button> {items.map((i, index) => ( <div key={i.id}> <button onClick={() => this.removeItem(i)} > - </button> {i.value}: <input /> </div> ))} </div> ) } }</quote>
RentPath User 5
RentPath User 5
~ 5 years ago

Problem, resolved. I missed the arrow function in the setState function used to copy the arrays items.

Great example... Awesome, Fantastico... Thanks

Jose
Jose
~ 5 years ago

The input focus example is the best visual I've seen thus far in this course. Great way to drive home the importance of unique identifiers.

Jared Hensley
Jared Hensley
~ 4 years ago

Why use App and not the this keyword?

Kent C. Dodds
Kent C. Doddsinstructor
~ 4 years ago

The properties you're talking about I'm assuming are the static properties. You cannot access static properties via this because they're not instance properties or even prototype properties. That's why I'm referencing App instead of this.

Rolando Barbella
Rolando Barbella
~ 4 years ago

Hi Kent, great course, thank you, so, you explained abit about using static, what is the advantage over state?

Kent C. Dodds
Kent C. Doddsinstructor
~ 4 years ago

Hi Rolando, In retrospect I wish I hadn't don the static thing because there's been a lot of confusion about it and it really isn't doing much useful. The reason I did it was for the code to communicate that those properties are related in an important way to their "owner."

Rolando Barbella
Rolando Barbella
~ 4 years ago

No worries Kent, thanks for the answer and help :)

elad hirsch
elad hirsch
~ 4 years ago
Lokesh Sanapalli
Lokesh Sanapalli
~ 4 years ago

Hey Kent,

great course, one doubt about this video, why do we need ({ in setState. For example,

this.setState(({items}) => ({
                items: items.filter(i => i !== item),
            }))

Thanks, Lokesh.

Kent C. Dodds
Kent C. Doddsinstructor
~ 4 years ago

Hi Lokesh, That's shorthand for this:

this.setState(previousState => {
  return { items: previousState.items.filter(i => i !== item) }
})

If you remove the ({ the syntax will be wrong for the shorthand and your JavaScript wont compile.