43

Problem

I'm getting this warning:

Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of EventsTable. See fb.me/react-warning-keys for more information.

react-runtime-dev.js?8fefd85d334323f8baa58410bac59b2a7f426ea7:21998 Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of Event. See fb.me/react-warning-keys for more information.

Source

This is EventsTable:

EventsTable = React.createClass({
  displayName: 'EventsTable',

  render() {
    console.dir(this.props.list);

    return (
      <table className="events-table">
        <thead>
          <tr>
            {_.keys(this.props.list[0]).map(function (key) {
              if (key !== 'attributes') {
                return <th>{key}</th>;
              }
            })}
         </tr>
        </thead>

        <tbody>
          {this.props.list.map(function (row) {
            return (
              <Event key={row.WhatId} data={row} />
            );
          })}
        </tbody>
      </table>
    )
  }
});

This is Event:

Event = React.createClass({
  displayName: 'Event',

  render() {
    return (
      <tr>
        {_.keys(this.props.data).map((x) => {
          if (x !== 'attributes')
            return <td>{this.props.data[x]}</td>;
        })}
      </tr>
    )
  }
});

Question

Clearly I've got the key prop on the <Event /> component. And I'm following the convention that you're supposed to include key on the component, not on the HTML itself (in other words, HTML tags within the Event component). Per the official React docs:

The key should always be supplied directly to the components in the array, not to the container HTML child of each component in the array:

I'm severely confused. Why am I getting warnings?

totymedli
  • 29,531
  • 22
  • 131
  • 165
ffxsam
  • 26,428
  • 32
  • 94
  • 144

6 Answers6

33

I ended up solving it when I realized because I had a <React.Fragment> which also needs a unique key.

Dharman
  • 30,962
  • 25
  • 85
  • 135
  • 6
    I also just want to add to this. I had a: <>> instead of React.Fragment. I added replaced the <>> with and all is working now w/ no warnings. – Christopher Adams Feb 24 '20 at 15:07
  • This was my issue - tried @ChrisAdams solution and it worked. Thanks! Would have never caught this – BWeb303 Apr 15 '20 at 22:53
  • Putting <>> this is just a bad idea because it wont show error at all if you use then it shows you the error huh – Shivam Jan 18 '22 at 13:11
  • Oh my gosh this is absurd. Thank you! – Elyse Dawson Apr 11 '23 at 20:40
  • Kind of a necro, but I had a very similar problem, and I was able to solve it when I moved the key prop to the outermost element of the component. It seems that it needs to be there in order for React to recognize it. – jaggerabney Jul 21 '23 at 17:44
31

Have you tried adding a key to the <th> tag?

         <tr>
            {_.keys(this.props.list[0]).map(function (key) {
              if (key !== 'attributes') {
                return <th key={key}>{key}</th>;
              }
            })}
         </tr>
sma
  • 9,449
  • 8
  • 51
  • 80
  • 5
    But don't the React docs say *not* to put `key` on the HTML tag? See [here](http://facebook.github.io/react/docs/multiple-components.html#dynamic-children) – ffxsam Aug 27 '15 at 18:07
  • 1
    My understanding of that has always been that if you are iterating over component that generates HTML, the key should be on the component. In your case, though, you aren't using a component to generate the th tags, so it may be valid in this case. – sma Aug 27 '15 at 18:08
  • So I added the key as you suggested, and that worked. And then I went into the `Event` component and changed that to `return {this.props.data[x]}` and that got rid of that warning. I don't know if I'm using the key correctly. In that case, it's just returning 0, 1, 2, etc for each TD cell. That won't be unique across the whole DOM. – ffxsam Aug 27 '15 at 18:12
  • I think you can leave the `key` on the `Event` component, no? Did you try that? – sma Aug 27 '15 at 18:13
  • I did leave the key on `` and I still got a warning for the `Event` component since it's iterating as well. – ffxsam Aug 27 '15 at 18:14
  • Ah, gotcha. That is strange. Seems overkill, but what if you made the `` tags a component also and put `key` on that? – sma Aug 27 '15 at 18:16
  • 1
    Putting the key on the `` works. It may not be ideal..but I can always refactor later. Your answer definitely helped, thanks! – ffxsam Aug 27 '15 at 18:38
19

tl;dr

Every time you render a list (use map), add a unique key attribute to the list elements (the topmost or "root" element returned from map's callback):

render() {
  return (
    <div>
      {this.props.data.map( element => {
         // Place the key on to the returned "root" element.
         // Also, in real life this should be a separate component...
         return <div key={element.id}>
           <span>Id (Name): </span>
           <span>{element.id} </span>
           <span>({element.name})</span>
         </div>;
      })}
    </div>
  )
}

Explanation

Understanding keys in React

The official Lists and Keys documentation shows how you should work with lists and the linked reconciliations doc tells the whys.

Basically when React rerenders a component it runs a diff algorithm that finds out what changed between the new and the previous version of the list. Comparison is not always trivial, but if there is a unique key in each element, it can be clearly identified what has changed. See the example in the doc:

<!-- previous -->
<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<!-- new -->    
<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

It is clear that a new element with the key 2014 was added, since we have all the other keys and those weren't changed. Without the keys this would be obscure.

Selecting a proper key

From now it is easy to see:

  • Why it is important that the key should be unique but only between the siblings in the list, because the comparison happens only within the given list's previous and new elements.
  • The key should remain the same for the same element between the previous and the new version, otherwise we would compare different elements and wouldn't be able to track change. That is why it is advised to use the id of the resource or (if it doesn't have one) some other data that is unique to the element, and why you shouldn't use things like Math.random().

Placing key attribute on components

The convention that you should place the key attribute to a component is more of a good practice, because when you iterate a list and want to render an element, that clearly indicates that you should organize that code to a separate component.

Setting the key attribute in the loop

The statement you quoted from the docs:

The key should always be supplied directly to the components in the array, not to the container HTML child of each component in the array:

Means that if you render components in a loop, then you should set the key attribute of the component in the loop, like you did it in your EventsTable component:

{this.props.list.map(function (row) {
  return (
    <Event key={row.WhatId} data={row} />
  );
})}

The wrong way is to pass it down to the component where it would set the key on itself:

Event = React.createClass({
  displayName: 'Event',
  render() {
    // Don't do this!
    return (
      <tr key={this.props.data.WhatId}>
        {_.keys(this.props.data).map((x) => {

There is another good example for this in this article.

Giorgi Moniava
  • 27,046
  • 9
  • 53
  • 90
totymedli
  • 29,531
  • 22
  • 131
  • 165
12

Check if variable that you pass to key is defined, because if it's undefined then error will be same, but it looks like code should work.

Artem P
  • 5,198
  • 5
  • 40
  • 44
  • Exactly! It's very easy to miss when using eg `someId` when the real value has eg. `someID`. And even more strange is that `PropTypes` does not "comment" on that either. – Juha Untinen Jan 06 '22 at 22:29
3

I had the problems too, and fixed it after follwing link.

like:

{_data.map(function(object, i){
     return <div className={"row"} key={i}> 
           {[ object.name ,
      <b className="fosfo" key={i}> {object.city} </b> , // remove the key
          object.age
      ]}
</div>; 
})}
Community
  • 1
  • 1
deju
  • 1,028
  • 2
  • 10
  • 16
0

The easiest fix for this is to create a separate component for the items you're mapping and add the key to that component.

Create a new component above your existing component (or link to it your call).

const TableDataComponent = ({ k }) => {
   return (
     <th>{k}</th>
   )
}

Then in your code add that component with your key:

<tr>
    {arr.map((k) => {
      return <TableDataComponent key={k._id} k={k} />
    })}
</tr>
Dharman
  • 30,962
  • 25
  • 85
  • 135
Trey Cooper
  • 141
  • 1
  • 5