React List Keys That Survive a Filter
key={index} looks fine until the list changes shape. React uses the key to decide which component instance keeps its state. If the key is the position, then the state belongs to the position. Filter out row 0 and the state that was on row 1 is now on row 0. The checkbox stays ticked on the wrong person. The input keeps the previous row's draft.
This shows up in admin tables, in carts, and in any list with a local expanded row. It does not show up in a static map of strings, which is why the bug ships.
What the key has to be
The key is the identity of the item, stable for as long as that item is the same item.
| Source | Use it when |
|---|---|
| Database id, UUID | The row exists on the server |
| Client id created when the user added a draft row | The row is not saved yet |
A composite that cannot collide, such as ${orderId}:${lineId} | The same line id can appear under two parents |
Do not use the array index. Do not use a field the user can edit, such as the title, unless a title change is supposed to reset the row. Do not use Math.random() during render. A new key every render remounts the row, drops focus, and replays effects.
{rows.map((row) => (
<OrderLine key={row.id} line={row} />
))}
If two rows can share an id because you concatenated pages and the API repeated a cursor item, fix the data. A duplicate key warning is React telling you the identity map is wrong. Suppressing it hides which row will keep the state.
Filter, sort, and prepend
These three operations are the test:
- Type into an input inside row B. Filter the list so row A disappears. Row B's input must still show what you typed.
- Sort by name. The same input must stay on row B, not on whoever is now in that slot.
- Prepend a new row. Existing rows must not remount. Effects that fetch on mount must not refetch.
Index keys fail all three. Id keys pass all three, as long as the component's state lives in the component that has the key. If the state lives in the parent, keyed by index in a parallel array, you have the same bug one level up. Keep the draft on the row object, or in a map keyed by row.id.
When the index is actually the identity
A fixed list of steps — "step 1, step 2, step 3" — that never reorders can use the index, because the position is the item. The moment you sort, filter, or insert, that stops being true. If you are unsure, use an id. The cost is nothing. The failure mode of index keys is a support ticket that says "the wrong account got the note."
Keep reading
Next.js 16 Cache Components: Replacing revalidate and force-dynamic With 'use cache'
How Cache Components in Next.js 16 replace route segment configs with the use cache directive and cacheLife, what becomes a build error, and the cookies-outside-the-cache rule that trips every migration.
React 19 useOptimistic: Instant UI Without Lying to the Server
How to wire useOptimistic with Actions so toggles and comments feel native, roll back on failure, and stay consistent when two mutations overlap.
Server Components vs. Client Components: A Mental Model That Sticks
The hardest part of React Server Components isn't the syntax — it's knowing which kind of component you're writing and why. Here is the mental model that makes the boundary obvious.
Optimistic UI Updates: Making Apps Feel Instant Without Lying to Users
The like button that responds before the server confirms feels instant. The trick is updating the UI first and reconciling later — and handling the rollback so you never mislead the user.
Debouncing and Throttling in React: Taming Expensive Event Handlers
A search box that fires a request on every keystroke, a scroll handler running 60 times a second — these are debounce and throttle problems. The concepts are simple; React makes them subtly tricky.
Next.js 16 and React 19: What Actually Matters in 2026
A practical guide to the features that changed how we build React apps — Server Components, the new compiler, and the patterns that stuck.
Newsletter
New posts, straight to your inbox
One email per post. No spam, no tracking pixels, unsubscribe anytime.
Comments
- No comments yet. Be the first.