React `key` practices
Understanding React key
Keys give the react elements a stable
identity
Warning in array without key
import React from 'react'
function App() {
const persons = ['mike', 'jason', 'sharky']
return (
<ul>
{persons.map((p) => {
return <li>{p}</li>
})}
</ul>
)
}
React will throw a warning with the code above: Warning: Each child in a list should have a unique "key" prop.
What keys to use?
Keys should be unique and stable
Index ? Maybe, but only under some circumstances
Array index is unique across an array.
import React, { useState } from 'react'
function App() {
const persons = ['mike', 'jason', 'sharky']
return (
<ul>
{persons.map((p, index) => {
return <li key={index}>{p}</li>
})}
</ul>
)
}
However, if the order/number of items may change, array index is NOT stable.
- mike
- jason
- sharky
import React, { useState } from 'react'
export default function App() {
const [persons, setPersons] = useState(['mike', 'jason', 'sharky'])
const onAdd = () => {
setPersons(['fishman', ...persons])
}
return (
<>
<button
onClick={onAdd}
className="mb-4 rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700"
>
Add fishman on top
</button>
<ul className="space-y-2">
{persons.map((p, index) => {
return (
<li key={index} className="flex items-center gap-2">
<input type="checkbox" />
{p}
</li>
)
})}
</ul>
</>
)
}
Try it yourself: Tick mike and then click the add button. You'll see the check status incorrectly stays with the first position instead of following mike.
Stable key
Usually we will have a unique and stable id like property for each record from backend/database.
- mike
- jason
- sharky
import { useState } from 'react'
export default function App() {
const [persons, setPersons] = useState([
{ id: 'uywoejk', name: 'mike' },
{ id: 'woeioqj', name: 'jason' },
{ id: 'eljlkqd', name: 'sharky' },
])
const onAdd = () => {
setPersons([
{
id: 'wuioeioe',
name: 'fishman',
},
...persons,
])
}
return (
<>
<button
onClick={onAdd}
className="mb-4 rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700"
>
Add fishman on top
</button>
<ul className="space-y-2">
{persons.map((p) => {
return (
<li key={p.id} className="flex items-center gap-2">
<input type="checkbox" />
{p.name}
</li>
)
})}
</ul>
</>
)
}
Try it yourself: Now tick mike and click add. The checkbox correctly stays with mike because each person has a stable id as the key.
Common Key Anti-patterns
❌ Random values or timestamps
// DON'T DO THIS
{
items.map((item) => <div key={Math.random()}>{item.name}</div>)
}
// OR THIS
{
items.map((item) => <div key={Date.now()}>{item.name}</div>)
}
These generate new keys on every render, forcing React to unmount and remount all elements unnecessarily. This destroys state and kills performance.
❌ Duplicate keys
// BAD: Multiple items with same key
{
items.map((item) => <div key={item.category}>{item.name}</div>)
}
Duplicate keys confuse React's reconciliation algorithm and can cause incorrect updates or lost state.
Advanced Key Concepts
Keys in fragments
When returning multiple elements from a component or mapping an array to fragments, you need to add keys to the fragments:
function Glossary({ items }) {
return (
<dl>
{items.map((item) => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
)
}
Keys only need to be unique among siblings
Keys don't need to be globally unique—only unique among their siblings:
function Blog() {
const posts = [{ id: 1, title: 'Post 1' }]
const comments = [{ id: 1, text: 'Comment 1' }] // Same id is fine!
return (
<>
{posts.map((post) => (
<Post key={post.id} {...post} />
))}
{comments.map((comment) => (
<Comment key={comment.id} {...comment} />
))}
</>
)
}
Use key to unmount component
Use case: I have a select and input component. Each the select option changes, reset the input to default state.
import { useState } from 'react'
export default function App() {
const [option, setOption] = useState('')
return (
<div className="space-y-4">
<div>
<label htmlFor="key-unmount-demo-option-select" className="mr-2">
Select option:
</label>
<select
id="key-unmount-demo-option-select"
onChange={(e) => setOption(e.target.value)}
className="w-30 rounded border border-gray-300 px-2 py-1 dark:border-gray-600 dark:bg-gray-800"
>
<option value="">Choose...</option>
<option value="a">A</option>
<option value="b">B</option>
</select>
</div>
<div>
<div className="mb-1 text-sm text-gray-600 dark:text-gray-400">
Without key (state persists):
</div>
<input
defaultValue="this is default"
className="rounded border border-gray-300 px-2 py-1 dark:border-gray-600 dark:bg-gray-800"
/>
</div>
<div>
<div className="mb-1 text-sm text-gray-600 dark:text-gray-400">
With key={option} (resets on change):
</div>
<input
key={option}
defaultValue="this is default"
className="rounded border border-gray-300 px-2 py-1 dark:border-gray-600 dark:bg-gray-800"
/>
</div>
</div>
)
}
Try it yourself: Type something in both inputs, then change the select. The second input (with key) resets because React unmounts and remounts it when the key changes.
Performance implications
Keys are critical for React's reconciliation performance. With proper keys, React can:
- Efficiently reorder existing DOM nodes instead of recreating them
- Preserve component state when items move in a list
- Skip unnecessary re-renders of unchanged items
Without proper keys, React may needlessly destroy and recreate DOM nodes, losing state and degrading performance.
Conclusion
keyis a unique identifier for react elementskeyshould beuniqueandstable. A react element with a differentkeyin different render phases will be considered a different element. The old one will be unmounted and a new one is created.- Only use array indices as keys when the number/order of array items are unchanged across the whole app lifecycle.
- Always prefer to use backend/database unique identifier id-like properties as
key - Never use random values, timestamps, or non-stable data as keys
- Keys only need to be unique among siblings, not globally
- Proper keys are essential for optimal reconciliation performance