Home / Articles

React Keys as Component Identity: Preserving and Resetting State

React keys decide which component instance survives a render. How index keys leak state between rows, and how changing a key resets a form on purpose.

Publisher: REACTAPP.TOP6 min read1462 words

This article is published in English.

Sources: React Keys Solve a Much Stranger Problem Than Rendering Lists

Most React developers add a key to every .map() call without thinking about it, and most explanations stop at "React needs it to track which item changed." That answer is correct but incomplete. The real question React asks on every render is whether an element it sees now is the same thing it saw last time, and the key is how you answer that question for it. Once you see keys as an identity mechanism, index-key bugs become predictable, and you gain a clean, declarative way to reset a component's state on demand.

The reflexive version

Here is the pattern almost every codebase contains. Each user in an array becomes a User element, and the user's id serves as the key:

users.map((user) => (
  <User key={user.id} user={user} />
))

Nothing is wrong with this snippet. The interesting part is what goes wrong when the key is chosen poorly, and that only shows up once components hold state of their own.

A stateful row makes the problem visible

Consider a card component that keeps a local selected flag. The flag lives inside the component, not in the user prop, so React has to decide which card instance owns which flag across renders:

interface UserCardProps {
  user: User;
}

const UserCard = ({ user }: UserCardProps) => {
  const [selected, setSelected] = useState(false);

  return (
    // ...
  );
}

Now render three people: Alice, Bob and a third user. Written as index-to-item pairs, the array looks like this:

0 → Alice
1 → Bob
2 → Maxime

What React sees with index keys

The common advice is to avoid array indexes as keys. The reason becomes concrete if you look at the elements React actually receives when the index is used:

<User key={0} user={alice} />
<User key={1} user={bob} />
<User key={2} user={maxime} />

Suppose the list supports deleting, adding, or drag-and-drop reordering. Remove Alice, and the next render produces this:

<User key={0} user={bob} />
<User key={1} user={maxime} />

From your point of view, Alice disappeared and the other two shifted up. Bob moved from index 1 to index 0, and the third user from 2 to 1. From React's point of view, nothing tells it that. It sees that an element with key={0} existed before and still exists, with the same component type. Same type plus same key is exactly the signal React uses to decide an instance should be kept. So it keeps the instance that used to render Alice and hands it Bob's props, keeps the second instance and hands it the third user's props, and unmounts the third one.

The consequence: if Alice's card was selected, Bob's card now appears selected, because the selected state belonged to the instance at key 0, not to Alice. The same kind of leak affects uncontrolled inputs, focus, scroll position inside a row, and any other state tied to the instance.

Why these bugs slip through

Incorrect keys are surprisingly common in real applications of every size. A list that is only ever appended to and never changes order behaves identically with index keys and stable keys, so nothing looks broken. The bug surfaces later, when someone adds deletion, filtering, sorting or reordering. Linting helps: the react/no-array-index-key rule from eslint-plugin-react flags index keys so the decision is made consciously rather than by habit.

When an item truly has no stable id, generate one when the data is created (for example with crypto.randomUUID() at insertion time), not during render. A key generated inside .map() changes on every render and forces React to remount every row, which is worse than using the index.

Props describe content, keys describe identity

A compact mental model helps here:

  • props answer "what does this thing contain right now?"
  • the key answers "which thing is this?"

Props can change freely while the component remains the same instance with its state intact. If a row supports inline editing, changing a user's name should not make React throw the row away and start over; it is still the same user. A stable key such as user.id expresses precisely that.

Deliberately forcing a fresh instance

Sometimes you want the opposite: you want React to treat a component as brand new. A key works outside of lists too:

<Profile key={user.id} user={user} />

Imagine Profile holds an unsaved draft in local state:

interface ProfileProps {
  user: User;
}

const Profile = ({ user }: ProfileProps) => {
  const [draft, setDraft] = useState({...});
}

If the parent switches from one user to another, carrying the previous user's draft into the next profile would be a real bug, and potentially a data-leak one. Without a key, React sees the same component type at the same position and keeps the instance, so the draft survives. With key={user.id}, a new id tells React the old Profile is gone. It unmounts the old instance, discards its state, runs its effect cleanups, and mounts a fresh one whose useState initializers run again.

This is the idiomatic alternative to writing an effect that watches user and manually resets every piece of state. That effect approach renders once with stale state before correcting it, and it is easy to forget a field. For more on why syncing state through effects tends to backfire, see the safer pattern that replaces useEffect-based syncing.

Resetting a form with a key

The same trick gives you a clean reset for forms:

<CreateUserForm key={draft.id} />

The key acts as an identity boundary. Whenever draft.id changes, for instance after a successful submission creates a new draft, React builds a new CreateUserForm with no leftover field values, validation errors or touched flags.

Two caveats are worth keeping in mind. Remounting resets the whole subtree, including children and anything they fetched on mount, so use it for components where a complete restart is what you mean. And it costs a full unmount and mount, which is fine for a form but not something to trigger on every keystroke.

Where state really lives

All of this follows from how React stores state. State is not attached to the function you wrote; if it were, every <User /> would share a single selected value. Instead, React associates state with a position in the rendered tree. On each render it walks the new output and decides, for every element, whether it is a continuation of something from the previous render. Component type and position are part of that decision, and among siblings the key refines it. The article on where useState keeps its value goes deeper into the fiber structure behind this.

The rule that falls out is simple:

  • same type, same position, same key: React treats it as the same component and preserves its state
  • a different type or a different key: React discards the old state and creates a new instance

Lists are simply the first place where position stops being reliable. When items can move, appear or vanish, "second child" no longer means "the same thing as last time," so React needs you to supply an identity. A stable key lets it follow an item as the list around it changes; a changed key is an explicit statement that something new has taken its place.

Key takeaways

  • A key tells React which instance an element is, not merely which item changed.
  • Index keys tie state to a slot, so deleting or reordering items moves state onto the wrong rows.
  • Use ids that are stable across renders; never generate keys during render.
  • Changing a key on purpose is the cleanest way to reset a component, such as a form or a per-entity editor.
  • Reset by key sparingly: it remounts the entire subtree and discards everything inside it.