jsx-key
jsx-key rule
Keywords: React, JSX, key prop, reconciliation, lists, map, iteration, ESLint rule, performance, LLM-optimized
Detect missing or problematic React keys that could break reconciliation. This rule is part of eslint-plugin-react-features and provides LLM-optimized error messages with suggestions.
Quick Summary
| Aspect | Details |
|---|---|
| Severity | Error (correctness) |
| Auto-Fix | 💡 Suggests fixes |
| Category | React |
| ESLint MCP | ✅ Optimized for ESLint MCP integration |
| Best For | All React/JSX projects |
Rule Details
Why This Matters
| Issue | Impact | Solution |
|---|---|---|
| 🔄 Reconciliation | React can't track elements | Add unique keys |
| ⚡ Performance | Unnecessary re-renders | Stable keys |
| 🐛 State Bugs | Wrong component gets state | Use item IDs, not indexes |
| 🎨 Animation Issues | Elements animate incorrectly | Consistent key identity |
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
warnUnstableKeys | boolean | true | Warn about potentially unstable keys |
Examples
❌ Incorrect
// Missing key
function UserList({ users }) {
return (
<ul>
{users.map((user) => (
<li>{user.name}</li> // ❌ Missing key
))}
</ul>
);
}
// Using index as key (unstable)
function ItemList({ items }) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item.name}</li> // ⚠️ Unstable key
))}
</ul>
);
}✅ Correct
// Using unique ID as key
function UserList({ users }) {
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li> // ✅ Stable unique key
))}
</ul>
);
}
// Using compound key when no ID available
function ItemList({ items }) {
return (
<ul>
{items.map((item) => (
<li key={`${item.category}-${item.name}`}>{item.name}</li> // ✅ Unique compound key
))}
</ul>
);
}Configuration Examples
Basic Usage
{
rules: {
'react-features/jsx-key': 'error'
}
}Disable Unstable Key Warnings
{
rules: {
'react-features/jsx-key': ['error', {
warnUnstableKeys: false
}]
}
}Key Best Practices
Choosing Keys
| Source | Quality | Example |
|---|---|---|
| Database ID | ✅ Best | key={user.id} |
| Unique field | ✅ Good | key={item.slug} |
| Compound unique | ✅ Good | key={item.cat + '-' + item.name} |
| UUID/nanoid | ⚠️ OK | key={generateId()} |
| Array index | ❌ Avoid | key={index} |
| Random number | ❌ Never | key={Math.random()} |
When Index Keys Are Acceptable
// ✅ Static list that never reorders/filters
const WEEKDAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
<ul>
{WEEKDAYS.map((day, i) => (
<li key={i}>{day}</li>
))}
</ul>;
// ❌ Dynamic list - DON'T use index
{
users.map((user, i) => <UserCard key={i} user={user} />);
} // Bad!
{
users.map((user) => <UserCard key={user.id} user={user} />);
} // Good!Common Patterns
Fragment Keys
// When using fragments in lists, key goes on Fragment
function DataList({ data }) {
return (
<>
{data.map((item) => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</React.Fragment>
))}
</>
);
}Nested Lists
function NestedList({ categories }) {
return categories.map((category) => (
<div key={category.id}>
<h2>{category.name}</h2>
<ul>
{category.items.map((item) => (
<li key={item.id}>{item.name}</li> // Separate key namespace
))}
</ul>
</div>
));
}When Not To Use
| Scenario | Recommendation |
|---|---|
| 🧪 Testing/Prototypes | Consider allowing index keys temporarily |
| 📊 Static content | Index keys may be acceptable |
| 🔄 No reordering | Index keys work for append-only lists |
Comparison with Alternatives
| Feature | jsx-key | eslint-plugin-react | jsx-ally |
|---|---|---|---|
| Missing key | ✅ Yes | ✅ Yes | ❌ No |
| Unstable keys | ✅ Configurable | ⚠️ Limited | ❌ No |
| LLM-Optimized | ✅ Yes | ❌ No | ❌ No |
| ESLint MCP | ✅ Optimized | ❌ No | ❌ No |
| Suggestions | ✅ Yes | ⚠️ Limited | ❌ No |
Related Rules
react-no-inline-functions- Performance optimizationreact-render-optimization- Render performance
Further Reading
- React Keys Documentation - Official React docs
- Why React Keys Matter - Kent C. Dodds article
- Reconciliation - How React updates the DOM
- ESLint MCP Setup - Enable AI assistant integration
Known False Negatives
The following patterns are not detected due to static analysis limitations:
Dynamic Variable References
Why: Static analysis cannot trace values stored in variables or passed through function parameters.
// ❌ NOT DETECTED - Prop from variable
const propValue = computedValue;
<Component prop={propValue} /> // Computation not analyzedMitigation: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs.
Imported Values
Why: When values come from imports, the rule cannot analyze their origin or construction.
// ❌ NOT DETECTED - Value from import
import { getValue } from './helpers';
processValue(getValue()); // Cross-file not trackedMitigation: Ensure imported values follow the same constraints. Use TypeScript for type safety.