Here's a React bug that's incredibly easy to write because the code looks completely reasonable.
You have a list:
{users.map((user, index) => (
<UserCard
key={index}
user={user}
/>
))}
ESLint is happy.
React stops complaining about missing keys.
Your UI renders perfectly.
You ship it.
Then three weeks later someone adds sorting, filtering, deleting, drag-and-drop, or live updates.
Suddenly a checkbox belongs to the wrong user.
An input keeps someone else's value.
An animation happens on the wrong row.
A component's local state appears to randomly move around.
And you start debugging the database.
The database is fine.
React is doing exactly what you told it to do.
The problem is the key.
What React Actually Uses a Key For
A lot of developers think this:
key={user.id}
is basically an optimization hint.
It isn't.
Keys help React determine which item is which between renders.
Think about this list:
Alice
Bob
Charlie
with:
key={user.id}
React can identify:
Alice → 101
Bob → 102
Charlie → 103
Now imagine Alice is removed:
Bob
Charlie
React can understand:
101 disappeared
102 is still here
103 is still here
That's exactly what you want.
But now look at:
key={index}
Initially:
Alice → 0
Bob → 1
Charlie → 2
Remove Alice.
Your new list becomes:
Bob → 0
Charlie → 1
From React's perspective, something very different happened.
The component that used to represent Alice now represents Bob.
The component that used to represent Bob now represents Charlie.
The identities shifted.
And that's where the weirdness starts.
The Classic Example: A Checkbox
Imagine:
function UserCard({ user }) {
const [selected, setSelected] = useState(false);
return (
<div>
<input
type="checkbox"
checked={selected}
onChange={() => setSelected(!selected)}
/>
{user.name}
</div>
);
}
Your list:
{users.map((user, index) => (
<UserCard
key={index}
user={user}
/>
))}
The user checks:
☑ Alice
☐ Bob
☐ Charlie
Then Alice gets removed.
Your data is now:
Bob
Charlie
But React can reuse the existing component instances.
You might end up visually seeing:
☑ Bob
☐ Charlie
Bob never selected himself.
The component state simply followed the position instead of the user.
This is the kind of bug that makes developers say:
"I swear React is broken."
React isn't broken.
Your identity model is.
Why It Doesn't Happen Immediately
This is the important part.
If your list only ever grows:
Alice
Bob
Charlie
David
index keys may appear to work perfectly.
Nothing obviously breaks.
That's why this mistake survives code review.
The problem appears when the list changes in the middle.
For example:
remove
insert
sort
filter
reorder
drag-and-drop
pagination
live updates
The moment item positions change, index-based identity becomes unreliable.
Sorting Makes It Even More Obvious
Imagine:
1. Laptop
2. Keyboard
3. Mouse
Each row has:
const [expanded, setExpanded] = useState(false);
The user expands:
Laptop
Then they sort by price.
The new order becomes:
Mouse
Laptop
Keyboard
If you're using:
key={index}
React sees:
0 → something
1 → something
2 → something
The positions still exist.
But the actual items changed.
Now the expanded state can appear attached to the wrong product.
With:
key={product.id}
React knows:
Laptop is still Laptop.
It just moved.
That's the distinction.
The Fix Is Usually Ridiculously Simple
Instead of:
{products.map((product, index) => (
<ProductCard
key={index}
product={product}
/>
))}
use:
{products.map((product) => (
<ProductCard
key={product.id}
product={product}
/>
))}
Now the identity belongs to the data.
Not the position.
"But My Data Doesn't Have an ID"
This is where developers sometimes reach for:
key={Math.random()}
Please don't.
That's arguably worse.
Every render can generate a completely different key.
React may interpret your components as entirely new components.
That can cause:
- unnecessary remounts
- lost component state
- broken animations
- unnecessary DOM work
- confusing lifecycle behavior
You want a key that's:
stable + unique among siblings
Good:
key={user.id}
Potentially good:
key={user.email}
if the email is guaranteed unique and stable.
Bad:
key={index}
for dynamic lists.
Very bad:
key={Math.random()}
What About crypto.randomUUID()?
You might think:
key={crypto.randomUUID()}
solves the problem.
It doesn't if you're generating it during rendering.
For example:
{items.map(item => (
<Item
key={crypto.randomUUID()}
item={item}
/>
))}
You've created a new identity every time React renders.
That's not stable.
If you need IDs for newly created data, generate them when the item is created, store them with the item, and reuse them.
For example:
const newTodo = {
id: crypto.randomUUID(),
text: "Learn React",
};
Then:
<Todo
key={todo.id}
todo={todo}
/>
Now the identity survives re-renders.
There Is One Situation Where Index Keys Are Fine
This is where I don't want to turn this into:
"Never ever use index as a key."
That's too simplistic.
Index keys can be acceptable when the list is genuinely static.
For example:
const weekdays = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
];
If the list:
- never changes
- never gets reordered
- never gets filtered
- never gets inserted into
- never gets deleted from
then:
key={index}
is unlikely to create an identity bug.
The important question isn't:
"Is index always bad?"
It's:
"Can the position of this item change?"
If yes, use a stable identity.
The Hidden Cost Goes Beyond Bugs
There's another reason I care about this.
Bad keys make React's reconciliation less meaningful.
React needs to answer:
"Is this the same component as before?"
Stable keys give it a reliable answer.
Bad keys force React to infer identity from position.
And position isn't identity.
This matters especially in interfaces with:
forms
animations
drag-and-drop
editable tables
shopping carts
filters
sorting
live data
virtualized lists
The more interactive the list becomes, the more important stable identity becomes.
Next.js Doesn't Change This Rule
You might be building with:
Next.js
App Router
Server Components
TypeScript
Tailwind
It doesn't matter.
Once React is rendering a dynamic list, the key still needs to represent the item's identity.
For example:
export default function Orders({
orders,
}: {
orders: Order[];
}) {
return (
<div>
{orders.map((order) => (
<OrderRow
key={order.id}
order={order}
/>
))}
</div>
);
}
That's much safer than:
{orders.map((order, index) => (
<OrderRow
key={index}
order={order}
/>
))}
Here's the Mental Model I Wish More Developers Used
Don't think:
key = React requirement
Think:
key = component identity
Then the correct implementation becomes much more obvious.
If you have:
User 42
its identity is:
42
If it moves from position 4 to position 1, it's still User 42.
Therefore:
key={user.id}
makes sense.
But if you use:
key={index}
you're effectively saying:
"This component's identity is whatever happens to be sitting in this position."
That's usually not what you mean.
A Quick Code Review Trick
Whenever I see:
.map((item, index) => ...)
I immediately look at why index exists.
If the only reason is:
key={index}
I ask:
"Does this item have a stable ID?"
Usually the answer is yes.
Then the code becomes:
.map((item) => (
<Component
key={item.id}
item={item}
/>
))
One less variable.
One better identity model.
Potentially one nasty bug avoided.
The Bug Is Often Far Away From the Key
This is why these bugs are so frustrating.
The code that creates the problem might be:
key={index}
But the symptom could appear somewhere completely different:
❌ Wrong checkbox
❌ Wrong input value
❌ Wrong animation
❌ Wrong expanded row
❌ Wrong selected item
❌ Wrong focus
❌ State apparently "moving"
So when you see strange behavior in a dynamic React list, don't only inspect the state logic.
Look at the keys.
Especially if the list can be reordered, filtered, inserted into, or deleted from.
My React Key Rule
I keep it simple:
Static list?
→ Index can be okay.
Dynamic list?
→ Use a stable ID.
User-generated data?
→ Give each item a stable ID when it's created.
Never generate keys during render.
And most importantly:
A key isn't there to make React stop complaining.
It's there to tell React:
"This thing is still the same thing."
Once you understand that, React keys stop feeling like annoying syntax and start making a lot more sense.
Have you ever had one of those React bugs where the data was completely correct but the UI was showing the wrong state?
There's a good chance the first thing worth checking was the key.
If you've encountered a weird key-related bug in production, share it below. I'd love to hear the strangest one.
Get the templates: https://pixelanas.gumroad.com
Anas, full-stack Next.js developer building SaaS products and premium templates. X: *@ASheikh69751
Top comments (0)