#reactjs

Using Index of an array as KEY Can Lead to Bad Consequences

Sujay Prabhu's avatar

Sujay Prabhu

Every application makes use of Lists/arrays in one or the other way. When working with lists in React, one need to be more cautious, because of the key prop which is used with elements within array.

key prop is an identity to an element in the DOM. So, it is expected to be unique and static in order to differentiate between the elements. If you fail to pass key prop, you will end up seeing this in your console .

Warning: Each child in a list should have a unique key prop

At this point, if you think, hey, it's just a warning..., you are taking a chance (PLEASE DON'T). Because this may not seem like causing a serious issue, Believe me, you are not safe either.

Ways of implementing Keys

There are various ways of implementing keys with lists.

1. index of an array

Index as a key is an anti-pattern. This might be the easiest approach to get rid of the warning in the console, but this is a double edged sword. This approach may go wrong, if implemented without proper knowledge.

So, lets have a look at situations in which

  • indexes cannot be used as keys

    1. If new elements are added to the beginning or somewhere in the middle
    2. If array is filtered
    const App = () => {
      const [counter, setCounter] = useState(1);
      const [list, setList] = useState([
        {
          id: counter,
        },
      ]);
     
      const addToStart = () => {
        const newId = counter + 1;
        setCounter(newId);
        setList((oldArray) => [
          {
            id: newId,
          },
          ...oldArray,
        ]);
      };
     
      const addToEnd = () => {
        const newId = counter + 1;
        setCounter(newId);
        setList((oldArray) => [
          ...oldArray,
          {
            id: newId,
          },
        ]);
      };
     
      return (
        <div className="App">
          <button onClick={addToStart}>Add To Start</button>
          <button onClick={addToEnd}>Add To End</button>
          <table>
            <tr>
              <th>Id</th>
              <th>Item</th>
            </tr>
            {list.map((item, index) => (
              <tr key={index}>
                <td>
                  <label>{item.id}</label>
                </td>
                <td>
                  <input />
                </td>
              </tr>
            ))}
          </table>
        </div>
      );
    };

In the above code snippet, we have an array and we can add elements either to start/end of the array. If we add elements to end of the array, Item 1 will have key 0 and Item 2 will have key 1. Then, if element is added to start of the array, Item 3 falls on top, but it ends up having key 0 instead of 2.

In order to avoid this unexpected behaviour, we should not use array index as key

{
  list.map((item) => (
    <tr key={item.id}>
      <td>
        <label>{item.id}</label>
      </td>
      <td>
        <input />
      </td>
    </tr>
  ));
}

Now, if the same steps are followed as above, if element is added to the start of the array, Item 3 falls on top and will have key 2

  • indexes can be used as keys

    1. If new elements are pushed to the end of the array (as pushing elements to the end of the array will not affect indexes of existing elements)
    2. If the array is static
    3. If the array is not filtered

    In the above snippet, we can have array index as key, if we are adding element only to the end of the array.

    {
      list.map((item, index) => (
        <tr key={index}>
          <td>
            <label>{item.id}</label>
          </td>
          <td>
            <input />
          </td>
        </tr>
      ));
    }

Here is the link of the repository, in which, index is used as key and the other way round for transitions. This demo shows how using index as key will lead to unexpected behaviour.

2. Unique id from dataset

This is undoubtedly the best approach as you have unique id available from the data provided.

const items = [
  {
    id: 1,
    value: 'Item 1',
  },
  {
    id: 2,
    value: 'Item 2',
  },
  {
    id: 3,
    value: 'Item 3',
  },
];
 
<div>
  {items.map((item) => {
    return <div key={item.id}>{item.value}</div>;
  })}
</div>;

3. Generate unique ids using packages

In situations where you don't have unique ids along with the list, it is better to generate unique keys with the help of some packages. A lot of packages are available in the internet for generating unique keys/ids like react-uuid, uuid. I am going with react-uuid for now.

import uuid from 'react-uuid';
 
const arrayWithoutIds = ['user 1', 'user 2', 'user 3'];
const arrayWithIds = arrayWithoutIds.map((element) => {
  return {
    id: uuid(),
    value: element,
  };
});
 
const Component = ({ arrayWithIds }) => {
  return (
    <div>
      {arrayWithIds.map((item) => {
        return <div key={item.id}>{item.value}</div>;
      })}
    </div>
  );
};

One thing to remember here is, keys should be generated while creating the dataset or before the component mounts (componentWillMount), in order to prevent generation of unique id on every render.

4. Math.Random()

Usage of Math.Random() is not recommended because of its unstability. Because, one cannot deny the possibility of generating the same number twice.

Conclusion:

  • Try to implement keys using unique id from dataset / generate unique ids using packages approach.
  • index as key can be used as a last resort.
  • Do not use Math.Random() as keys.

Happy Learning