83

I am using index to generate key in a list. However, es-lint generates an error for the same. React doc also states that using the item index as a key should be used as last resort.

const list = children.map((child, index) =>
    <li key={index}> {child} </li>);

I considered using react-key-index. npm install react-key-index gives following error:

npm ERR! code E404

npm ERR! 404 Not Found: react-key-index@latest

Are there any suggestions on other packages that allow to generate unique key? Any suggestion on react key generator is appreciated!

Tanvi Patel
  • 1,167
  • 3
  • 11
  • 18
  • 1
    What do your objects look like in `children`? Do they have an ID, or any kind of data you can hash to generate a unique ID? – Nick Oct 13 '17 at 17:59
  • 2
    If a list is never reordered (including operations like "insert" or "delete") then indices are perfectly fine. There are also other use cases, e.g. loading-on-scroll where the next page can actually contain the same item (with the same ID). In that case using index is superior to using an identifier. Actually, in most cases indices are completely fine because partial updates do not happen in every component. – Sulthan Oct 13 '17 at 18:10
  • In this case, list would reorder – Tanvi Patel Oct 13 '17 at 18:38
  • Using a unique key (over an index value) increases performance. If you use an index and your array values shuffle around, the index is no longer unique and React cant diff the objects optimally. – lux Oct 13 '17 at 18:39

6 Answers6

100

When you use index of an array as a key, React will optimize and not render as expected. What happens in such a scenario can be explained with an example.

Suppose the parent component gets an array of 10 items and renders 10 components based on the array. Suppose the 5th item is then removed from the array. On the next render the parent will receive an array of 9 items and so React will render 9 components. This will show up as the 10th component getting removed, instead of the 5th, because React has no way of differentiating between the items based on index.

Therefore always use a unique identifier as a key for components that are rendered from an array of items.

You can generate your own unique key by using any of the field of the child object that is unique as a key. Normal, any id field of the child object can be used if available.

Edit : You will only be able to see the behavior mentioned above happen if the components create and manage their own state, e.g. in uncontrolled textboxes, timers etc. E.g. React error when removing input component

palsrealm
  • 5,083
  • 1
  • 20
  • 25
  • 6
    I'm not following this example - can you provide a working example that reproduces this problem? I work with arrays constantly and have never hit this type of issue. – lux Oct 13 '17 at 18:37
  • @lux Maybe I was not able to explain properly. Hopefully this link will help. https://stackoverflow.com/questions/46477711/react-error-when-removing-input-component – palsrealm Oct 13 '17 at 18:49
56

The issue with using key={index} happens whenever the list is modified. React doesn't understand which item was added/removed/reordered since index is given on each render based on the order of the items in the array. Although, usually it's rendered fine, there are still situations when it fails.

Here is my example that I came across while building a list with input tags. One list is rendered based on index, another one based on id. The issue with the first list occurs every time you type anything in the input and then remove the item. On re-render React still shows as if that item is still there. This is a UI issue that is hard to spot and debug.

enter image description here

    class List extends React.Component {
      constructor() {
        super();
        this.state = {
          listForIndex: [{id: 1},{id: 2}],
          listForId: [{id: 1},{id: 2}]
        }
      }
      renderListByIndex = list => {
        return list.map((item, index) => {
          const { id } = item;
          return (
              <div key={index}>
                <input defaultValue={`Item ${id}`} />
                <button 
                  style={{margin: '5px'}} 
                  onClick={() => this.setState({ listForIndex: list.filter(i => i.id !== id) })}
                 >Remove</button>
              </div>
          )
        })
      }
      renderListById = list => {
        return list.map((item) => {
          const { id } = item;
          return (
              <div key={id}>
                <input defaultValue={`Item ${id}`} />
                <button 
                  style={{margin: '5px'}} 
                  onClick={() => this.setState({ listForId: list.filter(i => i.id !== id) })}
                 >Remove</button>
              </div>
          )
        })
      }
      render() {
        const { listForIndex, listForId } = this.state;
        return (
          <div className='flex-col'>
            <div>
              <strong>key is index</strong>
              {this.renderListByIndex(listForIndex)}
            </div>
            <div>
              <strong>key is id</strong>
              {this.renderListById(listForId)}
            </div>
          </div>
        )
      }
    }

    ReactDOM.render(
      <List />,
      document.getElementById('root')
    );
.flex-col {
  display: flex;
  flex-direction: row;
}

.flex-col > div {
  flex-basis: 50%;
  margin: .5em;
  padding: .5em;
  border: 1px solid #ccc;
 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
    <div id="root">
        <!-- This element's contents will be replaced with your component. -->
    </div>
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Rachel Nicolas
  • 995
  • 2
  • 12
  • 20
  • 2
    for a second i thought my cursor was moving on its own – Muhammad Umer Jan 16 '20 at 17:42
  • Here's what happens. I added two more items to the code above. https://codesandbox.io/s/vigorous-morning-6xexu?file=/src/App.js When you remove the first item from the top of the "key is index" list the newly created array of 3 items gonna have the keys of 0, 1, 2. Before the deletion the initial array keys were 0, 1, 2, 3. Reacts sees the keys 0, 1, 2 (the numerical value of the keys not the array data) remained the same hence no need to rerender the items with keys 0, 1, 2. So the displayed list effectively stays the same though the array data has changed – El Anonimo Aug 13 '20 at 14:11
11

Of course, in React, you are required to pass in a unique key value for all elements of an array. Else, you will see this warning in console.

Warning: Each child in an array or iterator should have a unique “key” prop.

So, as a lazy developer, you would simply pass in the loop’s index value as the key value of the child element.

Reordering a list, or adding and removing items from a list can cause issues with the component state, when indexes are used as keys. If the key is an index, reordering an item changes it. Hence, the component state can get mixed up and may use the old key for a different component instance.

What are some exceptions where it is safe to use index as key?

  • If your list is static and will not change.
  • The list will never be re-ordered.
  • The list will not be filtered (adding/removing item from the list).
  • There are no ids for the items in the list.

Key should be unique but only among its siblings.

isherwood
  • 58,414
  • 16
  • 114
  • 157
  • I have been received this problem but in my case, I have been putting the value of the li in the key, and that has been fixed by adding the index parameter to the map function and putting the index in the key place `gusts.map((gust,index) =>
  • {gust}
  • )` – Elias Salom Mar 18 '22 at 17:58