React preserves state according to a component's identity in the rendered tree. Keys help distinguish siblings as items move, appear or disappear. They are not only a way to remove a console warning.

In a list of editable records, the useful question is: which record should own this draft after reordering? A stable record ID answers that question. The current array position usually does not.

What happens when editable rows move?

import { useState } from 'react';

function Row({ item }) {
  const [draft, setDraft] = useState(item.name);
  return <label>{item.id}
    <input aria-label={'Name for ' + item.id} value={draft}
      onChange={event => setDraft(event.target.value)} />
  </label>;
}

export default function EditableList() {
  const [items, setItems] = useState([
    { id: 'a', name: 'Apricot' },
    { id: 'b', name: 'Berry' }
  ]);
  return <>
    <button onClick={() => setItems(current => [...current].reverse())}>
      Reverse
    </button>
    {items.map(item => <Row key={item.id} item={item} />)}
  </>;
}

Type a new draft for item a and reverse. With stable keys, the local draft stays with a. If the keys were array indexes, the state could stay with the position while the record at that position changed. That creates an apparent mix-up of user input.

Why not generate a random key?

A new random value on each render tells React it is a different child. The old child is removed and a new one is created. Local state and focus can be lost. Use an ID stored with the record, not a new identity generated while mapping the list.

Keys need to be unique among siblings. They do not need to be unique across every component in the application. React also does not pass key as a normal prop; pass item.id separately if the child needs it.

When is resetting by key useful?

A message composer might need a fresh draft when the recipient changes. Giving the composer a recipient key makes that reset intentional. Before doing this, confirm the product requirement: sometimes each recipient should keep a separate saved draft instead.

A key reset recreates the component subtree. It can reset more than the field you were thinking about, including selection and other local state. If only one value should change, a targeted state transition may better express the requirement.

Does a new prop reset useState?

No. The initial value passed to useState is used for initialization, not as a continuing synchronization instruction. In Row, a later item.name change does not automatically overwrite draft while the same row identity remains.

Choose an ownership rule. For externally controlled values, pass value and onChange from a parent. For local edits, decide when incoming data may replace unsaved input. An Effect that blindly copies every prop change can erase a user's work.

What makes a good test?

  • Edit one row, reorder, and verify its ID still has the edit.
  • Insert another record before it and repeat.
  • Remove a row and confirm only that row disappears.
  • Test the explicit reset behavior separately from ordinary rerenders.

Read accessible labels when testing rather than relying only on DOM position. The position is exactly what this exercise changes.

How to explain the trade-off

“I use stable IDs to preserve each record's component identity. I use a changed key only when I want a new subtree and its state. If a draft must survive removal or navigation, I move that draft to an owner with the necessary lifetime.” This connects the key to the user's data instead of treating it as decoration.

References and next practice

react.dev — react keys

react question collection · Browse interview topics · Practise a question in Interview Challenge · How to Answer Technical Interview Questions Out Loud