What is the key in lists for? What will happen if you use an array index as a key?

👨‍💻 Frontend Developer 🟡 Often Asked 🎚️ Medium
#React

Brief Answer

Keys in React lists are special attributes that help React identify which items have changed, been added, or removed. They are necessary for optimizing the reconciliation process and properly working with the virtual DOM.

Why keys are needed:

  • Performance optimization when updating lists
  • Proper identification of items when changes occur
  • Prevention of errors when working with item states
  • Correct operation of animations and transitions

Problems when using array index as key:

  • Unpredictable behavior when changing item order
  • Loss of component states during updates
  • Focus and selection errors
  • Animation and transition issues

Key rule: Always use stable, unique item identifiers as keys, not array indices.


Full Answer

Keys in React lists are an important concept that helps the library efficiently update and render lists of items. Understanding how keys work and why you shouldn’t use array indices as keys is critically important for developing performant and error-free React applications.

What are keys in lists

Keys are special string attributes that you add when creating lists of elements:

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        // Unique key for each item
        <li key={todo.id}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

Why keys are needed

React uses keys to optimize the reconciliation process - the algorithm that determines how UI elements should change when state updates:

// Without keys React can't efficiently track changes
function BadList({ items }) {
  return (
    <div>
      {items.map(item => (
        <div>
          {item.text}
        </div>
      ))}
    </div>
  );
}
 
// With keys React can precisely determine which items changed
function GoodList({ items }) {
  return (
    <div>
      {items.map(item => (
        <div key={item.id}>
          {item.text}
        </div>
      ))}
    </div>
  );
}

Problems when using array index as key

1. Unpredictable behavior when changing order

Using index as key can lead to unpredictable behavior when changing item order:

// ❌ Problem with indices as keys
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo, index) => (
        // ❌ Using index as key
        <li key={index}>
          <input type="text" defaultValue={todo.text} />
        </li>
      ))}
    </ul>
  );
}
 
// Example of the problem:
// Original list: ["Buy bread", "Buy milk", "Buy eggs"]
// After removing the second element:
// Became: ["Buy bread", "Buy eggs"]
 
// When using indices as keys:
// React thinks:
// - Element 0 remained unchanged ("Buy bread")
// - Element 1 changed from "Buy milk" to "Buy eggs"
// - Element 2 was removed
 
// But actually:
// - Element 0 remained unchanged ("Buy bread")
// - Element 1 was removed
// - Element 2 remained unchanged ("Buy eggs"), but lost its state

2. Loss of component states

When using indices as keys, components lose their state:

// ❌ State loss when using indices
function TodoItem({ todo, index }) {
  const [isEditing, setIsEditing] = useState(false);
  const [text, setText] = useState(todo.text);
  
  return (
    <div>
      {isEditing ? (
        <input 
          value={text} 
          onChange={e => setText(e.target.value)}
        />
      ) : (
        <span>{text}</span>
      )}
      <button onClick={() => setIsEditing(!isEditing)}>
        {isEditing ? 'Save' : 'Edit'}
      </button>
    </div>
  );
}
 
function TodoList({ todos }) {
  return (
    <div>
      {todos.map((todo, index) => (
        // ❌ Using index as key will cause state loss
        <TodoItem key={index} todo={todo} index={index} />
      ))}
    </div>
  );
}
 
// What happens:
// 1. User starts editing the second item (index = 1)
// 2. isEditing becomes true for this component
// 3. User adds a new item at the beginning of the list
// 4. Now the item that was second (index = 1) becomes third
// 5. React thinks the component with key=1 remained the same, but it's actually a different item
// 6. The isEditing state transfers to the wrong item

3. Focus and selection issues

Using indices as keys can cause focus and selection problems:

// ❌ Focus problems
function UserList({ users }) {
  const [selectedId, setSelectedId] = useState(null);
  
  return (
    <div>
      {users.map((user, index) => (
        // ❌ Using index as key
        <div 
          key={index} 
          className={selectedId === user.id ? 'selected' : ''}
          onClick={() => setSelectedId(user.id)}
          tabIndex={0}
        >
          {user.name}
        </div>
      ))}
    </div>
  );
}
 
// Problem:
// 1. User selects the second item (index = 1)
// 2. selectedId is set to the second item's user.id
// 3. List updates, and the second item moves to a different position
// 4. Now selectedId points to a different item, even though the user didn't change selection

How to properly use keys

1. Using unique identifiers

Best practice is to use unique item identifiers:

// ✅ Proper key usage
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        // ✅ Unique identifier as key
        <li key={todo.id}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}
 
// If items don't have unique IDs, you can create them
function UserList({ users }) {
  return (
    <div>
      {users.map(user => (
        // ✅ Combination of unique properties
        <div key={`${user.name}-${user.email}`}>
          {user.name}
        </div>
      ))}
    </div>
  );
}

2. Generating unique keys

If items don’t have unique identifiers, they can be generated:

// ✅ Generating unique keys
function ItemList({ items }) {
  // Add unique IDs to items
  const itemsWithIds = items.map((item, index) => ({
    ...item,
    id: item.id || `item-${index}-${Date.now()}`
  }));
  
  return (
    <div>
      {itemsWithIds.map(item => (
        <div key={item.id}>
          {item.content}
        </div>
      ))}
    </div>
  );
}

3. Using stable keys

Keys should be stable between renders:

// ❌ Unstable keys
function BadList({ items }) {
  return (
    <div>
      {items.map(item => (
        // ❌ Unstable key - changes on every render
        <div key={Math.random()}>
          {item.text}
        </div>
      ))}
    </div>
  );
}
 
// ✅ Stable keys
function GoodList({ items }) {
  return (
    <div>
      {items.map(item => (
        // ✅ Stable key - doesn't change between renders
        <div key={item.id}>
          {item.text}
        </div>
      ))}
    </div>
  );
}

Practical Examples

1. Working with dynamic lists

import { useState } from 'react';
 
// ✅ Proper implementation of a list with add/remove capabilities
function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Buy bread' },
    { id: 2, text: 'Buy milk' },
    { id: 3, text: 'Buy eggs' }
  ]);
  
  const [newTodo, setNewTodo] = useState('');
  
  const addTodo = () => {
    if (newTodo.trim()) {
      setTodos([
        ...todos,
        { 
          id: Date.now(), // Unique ID for new item
          text: newTodo 
        }
      ]);
      setNewTodo('');
    }
  };
  
  const removeTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  
  return (
    <div>
      <input 
        value={newTodo}
        onChange={e => setNewTodo(e.target.value)}
        placeholder="New task"
      />
      <button onClick={addTodo}>Add</button>
      
      <ul>
        {todos.map(todo => (
          // ✅ Unique key for each item
          <li key={todo.id}>
            {todo.text}
            <button onClick={() => removeTodo(todo.id)}>
              Remove
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

2. Working with components that have state

import { useState } from 'react';
 
// Component with its own state
function EditableItem({ item, onUpdate, onDelete }) {
  const [isEditing, setIsEditing] = useState(false);
  const [text, setText] = useState(item.text);
  
  const save = () => {
    onUpdate(item.id, text);
    setIsEditing(false);
  };
  
  return (
    <div className="editable-item">
      {isEditing ? (
        <input 
          value={text} 
          onChange={e => setText(e.target.value)}
          onBlur={save}
          onKeyDown={e => e.key === 'Enter' && save()}
          autoFocus
        />
      ) : (
        <span onClick={() => setIsEditing(true)}>
          {text}
        </span>
      )}
      <button onClick={() => onDelete(item.id)}>
        Delete
      </button>
    </div>
  );
}
 
// List with components that have state
function EditableList() {
  const [items, setItems] = useState([
    { id: 1, text: 'Item 1' },
    { id: 2, text: 'Item 2' },
    { id: 3, text: 'Item 3' }
  ]);
  
  const updateItem = (id, newText) => {
    setItems(items.map(item => 
      item.id === id ? { ...item, text: newText } : item
    ));
  };
  
  const deleteItem = (id) => {
    setItems(items.filter(item => item.id !== id));
  };
  
  return (
    <div>
      {items.map(item => (
        // ✅ Unique key preserves each component's state
        <EditableItem 
          key={item.id} 
          item={item} 
          onUpdate={updateItem}
          onDelete={deleteItem}
        />
      ))}
    </div>
  );
}

Common Mistakes

1. Using index as key

// ❌ Common mistake
function BadPractice({ items }) {
  return (
    <div>
      {items.map((item, index) => (
        // ❌ Never use index as key for mutable lists
        <div key={index}>
          {item.name}
        </div>
      ))}
    </div>
  );
}

2. Using non-unique keys

// ❌ Problem with non-unique keys
function DuplicateKeys({ users }) {
  return (
    <div>
      {users.map(user => (
        // ❌ If multiple users have the name "Alexander", keys will be duplicated
        <div key={user.name}>
          {user.name}
        </div>
      ))}
    </div>
  );
}
 
// ✅ Correct solution
function UniqueKeys({ users }) {
  return (
    <div>
      {users.map(user => (
        // ✅ Unique key for each item
        <div key={`${user.name}-${user.id}`}>
          {user.name}
        </div>
      ))}
    </div>
  );
}

3. Using changing keys

// ❌ Problem with changing keys
function ChangingKeys({ items }) {
  return (
    <div>
      {items.map(item => (
        // ❌ Key changes on every render
        <div key={`${item.id}-${Math.random()}`}>
          {item.name}
        </div>
      ))}
    </div>
  );
}
 
// ✅ Correct solution
function StableKeys({ items }) {
  return (
    <div>
      {items.map(item => (
        // ✅ Stable key
        <div key={item.id}>
          {item.name}
        </div>
      ))}
    </div>
  );
}

Summary

Keys in React lists are special attributes that help React optimize the reconciliation process and properly work with dynamic lists:

Why keys are needed:

  • Performance optimization when updating lists
  • Proper item identification
  • Component state preservation
  • Correct operation with animations and transitions

Problems when using array index as key:

  • Unpredictable behavior when changing item order
  • Loss of component states
  • Focus and selection problems
  • Animation errors

Key points:

  • Always use unique, stable identifiers as keys
  • Never use array indices as keys for mutable lists
  • Keys should be predictable and not change between renders
  • When unique identifiers are absent, create them programmatically

Proper key usage is a fundamental React development skill that helps create efficient and error-free applications.


Want more articles for interview preparation? Subscribe to EasyAdvice, bookmark the site, and improve every day 💪