React keys are the mechanism React’s reconciler uses to match elements between renders — nothing more, nothing less. When a list re-renders, React needs some way to figure out which new elements correspond to which old ones, and the key prop is that mechanism. Get it wrong, and the consequences go well beyond a console warning.


Why Keys Matter for More Than Just Suppressing Console Warnings

A lot of developers add keys purely to make React’s missing-key warning go away, without registering that keys do real work during reconciliation. They tell React which element in a new render corresponds to which element from the previous one — a distinction that becomes critical the moment list order or composition shifts between renders.

Without stable, correctly-assigned keys, React’s default matching behavior can pair up the wrong elements across renders. The result: components losing internal state, unnecessary unmount/remount cycles (with the performance hit that implies), or brief flashes of incorrect data as the transition settles.


The Index-as-Key Problem in Detail

Array index works as a key only under a narrow condition: the list never reorders, never filters, and only ever gains or loses items at the very end. Break any of those conditions, and index-based keys start pairing the wrong data item with what React thinks is the “same” element from before — purely because it happens to land on the same index.

// Index 0 always refers to "whatever is currently first"
// not a stable identity for a specific data item
{items.map((item, index) => (
  <ListItem key={index} data={item} />
))}

Reorder this list, and the element at index 0 in the new render gets treated as identical to whatever occupied index 0 before — even if it now represents a completely different underlying item. Reconciliation runs entirely on the key, and index just doesn’t track item identity once ordering changes.


How This Caused the State Loss I Was Debugging

In the case that prompted this post, each list item rendered an expandable section backed by its own local useState for whether it was open. Sort the parent list, and previously-expanded items would appear to randomly collapse, while items the user had never touched would sometimes show up expanded instead.

The cause traced directly back to index-based keys. React was preserving state by index position rather than by item identity, so after a reorder, the component instance that used to represent item A — now sitting at a different index — got reassigned to whatever item landed in its old slot. The expanded state came along for the ride, attached to the wrong item entirely.


The Fix: Genuinely Stable, Unique Identifiers

Swapping index for a stable identifier that belongs to the data itself — a database ID, or any field that identifies the item independent of its position — fixed this immediately. With that change, React could track each item’s true identity through a reorder, keeping the expanded state tied to the item the user had actually expanded rather than to whatever item happened to inherit its old index.

// Stable identity regardless of list position
{items.map((item) => (
  <ListItem key={item.id} data={item} />
))}

Index isn’t the only trap. Using a key value that isn’t unique across the list — a category field shared by several items, for instance — produces the same kind of broken reconciliation. React needs uniqueness among sibling keys to track identity correctly, and duplicates make its matching behavior unpredictable.

If nothing in your data is reliably unique, generate an ID specifically for this purpose rather than reaching for a convenient-but-shared field. That sidesteps the whole category of problem.


Why Changing Keys Unnecessarily Also Causes Problems

There’s an inverse mistake too: a key that changes even though the item’s identity hasn’t. React reads that as a brand-new element, unmounts the old one, and mounts a fresh instance — costing performance and wiping out any local state that should have carried over.

This tends to happen when a key is built from several combined fields, and one of those fields shifts for reasons that have nothing to do with the item’s real identity — quietly producing a new key for what is still, conceptually, the same persistent entity.


React DevTools can confirm whether this is what you’re dealing with. Watch for components unmounting and remounting when you’d expect them to persist as the same instance across a list update, rather than simply re-rendering with new props. That pattern — unmount/remount instead of prop update — is the signature of a key problem, distinct from the re-render-related performance issues covered elsewhere on this site.


A Quick Reference Summary

Mistake Consequence Fix
Index as key with reordering/filtering Incorrect state/identity association after changes Use a stable, unique data identifier
Non-unique keys among siblings Unpredictable reconciliation behavior Ensure real uniqueness, generate if needed
Key changes despite same logical item Unnecessary unmount/remount, lost local state Ensure key reflects stable item identity
Missing keys entirely Falls back to less reliable implicit matching Always provide explicit, correct keys for list items

What Resolved the Original Debugging Session

Once the index-based key was confirmed as the root cause, switching to each item’s real database identifier resolved the state-association problem right away. Re-running the same reordering operation that had triggered the bug confirmed it: expanded state now stayed correctly attached to the specific items users had opened, no matter how the list was subsequently sorted.

Are you experiencing unexpected state loss or behavior in a list component, particularly after reordering or filtering? Describe your situation and I can help you check whether a key-related issue might be the cause.