Lists and Keys
Displaying lists of data is one of the most common tasks in React. Whether it's a todo list, a product catalog, or a feed of posts, you'll need to transform arrays into elements.
Rendering Lists with map()
The map() method transforms each item in an array into a React element:
function FruitList() {
const fruits = ['Apple', 'Banana', 'Orange'];
return (
<ul>
{fruits.map(fruit => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}
Why Keys Matter
Keys help React identify which items have changed:
// Without keys, React can't tell items apart
// This causes bugs and poor performance
// ✅ With keys, React can efficiently update
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
What Happens Without Keys?
React will:
- Re-render the entire list unnecessarily
- Lose component state (inputs, scroll position)
- Show warnings in the console
Choosing Good Keys
Best: Unique IDs from Your Data
// Data from an API usually has IDs
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
Acceptable: Generated IDs
// If your data doesn't have IDs
const todos = [
{ text: 'Learn React', id: crypto.randomUUID() },
{ text: 'Build app', id: crypto.randomUUID() }
];
Avoid: Array Index
// ❌ Problematic when items can be reordered
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
Using index as key causes issues when:
- Items can be reordered, inserted, or deleted
- Items have state (like input fields)
Rendering Complex Lists
Objects with Multiple Properties
function ProductList({ products }) {
return (
<div className="grid">
{products.map(product => (
<div key={product.id} className="card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price}</p>
<button>Add to Cart</button>
</div>
))}
</div>
);
}
Extracting List Item Components
function TodoItem({ todo, onToggle, onDelete }) {
return (
<li className={todo.done ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
);
}
function TodoList({ todos, onToggle, onDelete }) {
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
onDelete={onDelete}
/>
))}
</ul>
);
}
Filtering and Sorting
Filtered Lists
function SearchResults({ items, query }) {
const filtered = items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
return (
<ul>
{filtered.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
Sorted Lists
function SortedList({ items, sortBy }) {
const sorted = [...items].sort((a, b) => {
if (sortBy === 'name') return a.name.localeCompare(b.name);
if (sortBy === 'price') return a.price - b.price;
return 0;
});
return (
<ul>
{sorted.map(item => (
<li key={item.id}>{item.name} - ${item.price}</li>
))}
</ul>
);
}
Empty States
Always handle empty lists:
function MessageList({ messages }) {
if (messages.length === 0) {
return <p className="empty">No messages yet.</p>;
}
return (
<ul>
{messages.map(msg => (
<li key={msg.id}>{msg.text}</li>
))}
</ul>
);
}
Nested Lists
function CategoryList({ categories }) {
return (
<div>
{categories.map(category => (
<div key={category.id}>
<h2>{category.name}</h2>
<ul>
{category.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
))}
</div>
);
}
Key Placement Rules
Keys must be on the outermost element returned from map:
// ❌ Wrong - key on inner element
{items.map(item => (
<div>
<span key={item.id}>{item.name}</span>
</div>
))}
// ✅ Correct - key on outer element
{items.map(item => (
<div key={item.id}>
<span>{item.name}</span>
</div>
))}
Key Takeaways
- Use
map()to transform arrays into React elements - Always provide a unique
keyprop for list items - Keys help React track items efficiently
- Use stable IDs from your data, not array indices
- Keys only need to be unique among siblings
- Handle empty lists with appropriate UI