< Previous: State and the Single Source of Truth   Next: Cleaning up and Preparing for a Real Project >

Rendering an Array of Data with map() and JSX

There's one more thing we're going to cover before you know enough React basics to be able to move on to a real project, and that's how to loop over an array to render its contents. Right now we have a single person with a single country, but wouldn't it be neat if we could have 10 people with 10 countries, and have them all rendered? Sure it would.

Luckily for us, this is easy to do in JSX thanks to an array method called map(). When you call this on an array, you can have it run through all the items in that array and do something interesting with them – in our case, returning a new array of JSX that can be drawn.

First, modify your constructor so that we have an array of people we can loop over:

src/pages/Detail.js

constructor(props) {
    super(props);

    const people = [];

    for (let i = 0; i < 10; i++) {
        people.push({
            name: chance.first(),
            country: chance.country({ full: true })
        });
    }

    this.state = { people };
}

Note that I'm using short-hand syntax to set the people value in this.state – if the key name is the same as the value you want to use, you can just write it once.

Now that our state has an array of data to work with, we can loop over it using map() by modifying our render() method like this:

src/pages/Detail.js

render() {
    return (<div>
    {this.state.people.map((person, index) => (
        <p>Hello, {person.name} from {person.country}!</p>
    ))}
    </div>);
}

There are quite a few parentheses in there thanks to the way map() works, but that code block does the following:

  • For every item in the array, it gives us the item itself in person and the position of the item in index.
  • It creates a new anonymous function (that's the => part) that receives those two things as a parameter and will return a value of the modified data.
  • It uses the input element to create some JSX based on the person.

If you save the file and look in your browser, you'll probably see ten greeting messages in there so it looks like everything is working. But if you open your browser's error console you'll see a large warning: Each child in an array or iterator should have a unique "key" prop.

That error is pretty clear, but just in case you're not sure what it means here goes: if you use a loop like we're doing here (with map()) you need to give every top-level item printed by that loop a key attribute that identifies it uniquely. The reason for this is called reconciliation and it becomes very important when you make more advanced apps – and can cause some really weird bugs if you don't understand it fully!

If you create JSX in a loop and don't provide a key attribute for each element, React will warn you.

Consider the following output:

<div>
    <p>Hello, Jim from Australia!</p>
    <p>Hello, Dave from Malaysia!</p>
    <p>Hello, Charlotte from Thailand!</p>
</div>

That's three paragraphs of text all wrapped up inside a

element – it's trivial to make a component that renders that. Now imagine your component's state changes, and now it prints the following:

<div>
    <p>Hello, Jim from Australia!</p>
    <p>Hello, Charlotte from Thailand!</p>
</div>

What happened? Well you and I can both see that "Dave from Malaysia!" got removed for whatever reason, but React doesn't know that – it just sees that there are two items rather than three, so as far as React is concerned you just deleted the last item and moved the others up.

React asks for a key attribute so that it knows which item is which. If we re-wrote the previous examples it would look like this:

<div>
    <p key="1">Hello, Jim from Australia!</p>
    <p key="2">Hello, Dave from Malaysia!</p>
    <p key="3">Hello, Charlotte from Thailand!</p>
</div>

So when we delete Dave, React could see that numbers 1 and 3 remained and update accordingly.

Back to our Detail component with its random names and places: we can provide a key by using the index value we are receiving from map(), like this:

src/pages/Detail.js

render() {
    return (<div>
    {this.state.people.map((person, index) => (
        <p key={index}>Hello, {person.name} from {person.country}!</p>
    ))}
    </div>);
}

That works fine for now, but if you ever want to add, remove or move items, you'll need to use a key attribute that doesn't change when items are moved or rearranged. Trust me, I've been there: if you use the position of an item as its key when move items around, you'll get some marvellously weird behavior!

Buy the book for $10

Get the complete, unabridged Hacking with React e-book and take your learning to the next level - includes a 45-day no questions asked money back guarantee!

If this was helpful, please take a moment to tell others about Hacking with React by tweeting about it!

Copyright ©2016 Paul Hudson. Follow me: @twostraws.