The Key Prop Deep Dive

Difficulty: Advanced

The key prop is a special attribute in React that helps the reconciliation algorithm identify which elements in a list have changed, been added, or been removed. When React renders a list of elements, it needs a way to match elements between the old and new render. Without keys, React matches by position (index). With keys, React matches by identity, which is far more efficient and correct when items can be reordered, inserted, or deleted.

When you use index as a key and items are reordered, React thinks the element at index 0 changed its content (because a different item is now at index 0). It updates the DOM content but keeps the same component instance and its internal state. This causes subtle bugs: input fields retain values from the wrong item, animations break, and effects fire incorrectly. With a stable unique key (like item.id), React recognizes the component moved and reorders the DOM nodes without resetting state.

Using index as a key is only safe when three conditions are ALL true: (1) the list is static (items never reorder or get inserted/removed in the middle), (2) items have no internal state (no input fields, no animations), and (3) the list is never filtered or sorted. In all other cases, use a unique identifier from your data.

An advanced technique is using the key prop to force a component to reset its internal state. When you change a component's key, React treats it as a completely different component - it unmounts the old instance and mounts a new one. This is useful when you need to reset a form when switching between items, or when you want to restart animations. For example, <UserForm key={selectedUserId} /> remounts the form whenever the user changes, clearing all field values.

Keys must be unique among siblings - not globally unique. Two separate lists can use the same key values. Also, keys should be stable (not change between renders) and predictable (not random). Using Math.random() as a key forces a remount on every render, destroying all internal state and defeating the purpose of React's reconciliation.

Code examples

Index as key bug - state gets attached to wrong item

// BAD: Using index as key with a sortable list
function TodoList() {
  const [todos, setTodos] = React.useState([
    { id: 'a', text: 'Buy milk' },
    { id: 'b', text: 'Walk dog' },
    { id: 'c', text: 'Read book' },
  ]);

  const reverse = () => setTodos([...todos].reverse());

  return (
    <div>
      <button onClick={reverse}>Reverse</button>
      {todos.map((todo, index) => (
        // BUG: key={index} - checkbox state sticks to position, not item
        <div key={index}>
          <input type="checkbox" /> {todo.text}
        </div>
      ))}
    </div>
  );
}
// If user checks 'Buy milk' then clicks Reverse,
// 'Read book' will be checked instead! (checkbox stays at index 0)

// FIX: Use key={todo.id}
{todos.map(todo => (
  <div key={todo.id}>
    <input type="checkbox" /> {todo.text}
  </div>
))}

With index as key, React thinks the component at index 0 just changed its text prop. The checkbox state (internal to the DOM element) stays at index 0. With todo.id as key, React knows the component moved and moves the DOM node with its state.

Key prop to reset component state

function EditUserForm({ user }) {
  // Internal state resets when key changes
  const [name, setName] = React.useState(user.name);
  const [email, setEmail] = React.useState(user.email);

  return (
    <form>
      <input value={name} onChange={e => setName(e.target.value)} />
      <input value={email} onChange={e => setEmail(e.target.value)} />
    </form>
  );
}

// Parent uses key to force reset when user changes
function UserEditor({ selectedUser }) {
  return (
    <EditUserForm
      key={selectedUser.id}  // New key = new component instance
      user={selectedUser}
    />
  );
}

When selectedUser changes, the key changes, React unmounts the old EditUserForm and mounts a new one. All internal state (name, email) resets to the new user's values.

When index as key is acceptable

// SAFE: Static list that never reorders
function NavigationMenu() {
  const links = ['Home', 'About', 'Contact']; // Never changes

  return (
    <nav>
      {links.map((link, index) => (
        <a key={index} href={`/${link.toLowerCase()}`}>
          {link}
        </a>
      ))}
    </nav>
  );
}
// Index is fine here because:
// 1. List never reorders
// 2. Items have no internal state
// 3. List is never filtered

// UNSAFE: Dynamic list with user actions
function DynamicList({ items, onRemove }) {
  return items.map((item, index) => (
    // BAD: key={index} - removing middle item shifts all keys
    <div key={index}>
      {item.name}
      <button onClick={() => onRemove(item.id)}>Remove</button>
    </div>
  ));
}

The static navigation menu is safe with index keys because nothing can reorder or remove items. The dynamic list is unsafe because removing an item shifts all subsequent indices.

Key points

Concepts covered

key prop, reconciliation, list rendering, index as key, key reset pattern, component identity