Lists & Keys
Rendering lists of data and understanding keys
Rendering Lists
In React, you render lists by using JavaScript's map() method to transform an array of data into an array of JSX elements. This is one of the most common patterns in React.
Basic List Rendering
function FruitList() {
const fruits = ['Apple', 'Banana', 'Cherry', 'Date'];
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}
// Rendering with index (when items don't have unique id)
function NumberList() {
const numbers = [1, 2, 3, 4, 5];
return (
<ul>
{numbers.map((num, index) => (
<li key={index}>Number: {num}</li>
))}
</ul>
);
}
Why Keys Matter
Keys help React identify which items have changed, been added, or removed. They give elements a stable identity across re-renders, allowing React to update the DOM efficiently.
Rendering Objects
function UserList() {
const users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
{ id: 3, name: 'Charlie', email: 'charlie@example.com' }
];
return (
<div>
{users.map((user) => (
<div key={user.id} className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
))}
</div>
);
}
Key Rules
| Rule | Explanation |
|---|---|
| Must be unique | Keys must be unique among siblings (not globally) |
| Must be stable | Keys shouldn't change between renders |
| Avoid index as key | Only use index if list is static and won't reorder |
| Use data IDs | Best to use unique IDs from your data |
Good vs Bad Keys
function TodoList({ todos }) {
return (
<ul>
{/* ✓ GOOD: Using unique ID from data */}
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
{/* ✗ BAD: Using index (problematic if list reorders) */}
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li>
))}
{/* ✗ BAD: Using random values (changes every render!) */}
{todos.map(todo => (
<li key={Math.random()}>{todo.text}</li>
))}
</ul>
);
}
⚠️ When Index is OK
Using index as key is acceptable when:
• The list is static (never changes)
• Items are never reordered or filtered
• Items have no stable IDs
Extracting List Items to Components
// TodoItem component
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>
);
}
// Parent component renders the list
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', done: false },
{ id: 2, text: 'Build project', done: false }
]);
const handleToggle = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};
const handleDelete = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id} {/* Key goes on the outermost element in map */}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
/>
))}
</ul>
);
}
Filtering Lists
function FilteredList() {
const [filter, setFilter] = useState('all');
const items = [
{ id: 1, name: 'Apple', category: 'fruit' },
{ id: 2, name: 'Carrot', category: 'vegetable' },
{ id: 3, name: 'Banana', category: 'fruit' },
{ id: 4, name: 'Broccoli', category: 'vegetable' }
];
const filteredItems = filter === 'all'
? items
: items.filter(item => item.category === filter);
return (
<div>
<select value={filter} onChange={e => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="fruit">Fruits</option>
<option value="vegetable">Vegetables</option>
</select>
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
Sorting Lists
function SortableList() {
const [sortBy, setSortBy] = useState('name');
const [sortOrder, setSortOrder] = useState('asc');
const users = [
{ id: 1, name: 'Charlie', age: 25 },
{ id: 2, name: 'Alice', age: 30 },
{ id: 3, name: 'Bob', age: 20 }
];
const sortedUsers = [...users].sort((a, b) => {
const aVal = a[sortBy];
const bVal = b[sortBy];
if (sortOrder === 'asc') {
return aVal > bVal ? 1 : -1;
}
return aVal < bVal ? 1 : -1;
});
return (
<div>
<button onClick={() => setSortBy('name')}>Sort by Name</button>
<button onClick={() => setSortBy('age')}>Sort by Age</button>
<button onClick={() => setSortOrder(o => o === 'asc' ? 'desc' : 'asc')}>
{sortOrder === 'asc' ? '↑' : '↓'}
</button>
<ul>
{sortedUsers.map(user => (
<li key={user.id}>{user.name} ({user.age})</li>
))}
</ul>
</div>
);
}
Empty State
function ItemList({ items }) {
if (items.length === 0) {
return (
<div className="empty-state">
<p>No items found.</p>
<button>Add your first item</button>
</div>
);
}
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
Nested Lists
function CategoryList() {
const categories = [
{
id: 1,
name: 'Electronics',
items: [
{ id: 101, name: 'Phone' },
{ id: 102, name: 'Laptop' }
]
},
{
id: 2,
name: 'Clothing',
items: [
{ id: 201, name: 'Shirt' },
{ id: 202, name: 'Pants' }
]
}
];
return (
<div>
{categories.map(category => (
<div key={category.id}>
<h3>{category.name}</h3>
<ul>
{category.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
))}
</div>
);
}
🎯 Lists & Keys Best Practices
- ✓ Always provide a key prop when mapping arrays
- ✓ Use unique, stable IDs from your data
- ✓ Avoid using array index as key when list can change
- ✓ Never use random values as keys
- ✓ Extract list items into separate components
- ✓ Handle empty states gracefully
- ✓ Key goes on the outermost element returned by map()