React Lists and Keys - Complete Guide
Lists and keys are fundamental concepts in React for rendering collections of data. Understanding how to properly render lists and use keys will help you build efficient and maintainable React applications.
Rendering Lists in React
Most web applications display lists of data - whether it’s a list of users, products, comments, or menu items. React makes it easy to render lists using JavaScript’s array methods.
Basic List Rendering
The most common way to render lists in React is by using the map() method:
function NumberList() {
const numbers = [1, 2, 3, 4, 5];
return (
<ul>
{numbers.map((number) => (
<li key={number.toString()}>{number}</li>
))}
</ul>
);
}Rendering Lists of Objects
In real applications, you’ll often work with arrays of objects:
function UserList() {
const users = [
{ id: 1, name: 'Alice', email: '[email protected]' },
{ id: 2, name: 'Bob', email: '[email protected]' },
{ id: 3, name: 'Charlie', email: '[email protected]' }
];
return (
<div>
<h2>User List</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
<strong>{user.name}</strong> - {user.email}
</li>
))}
</ul>
</div>
);
}Extracting List Components
For better organization, extract list items into separate components:
function UserItem({ user }) {
return (
<li>
<strong>{user.name}</strong> - {user.email}
</li>
);
}
function UserList() {
const users = [
{ id: 1, name: 'Alice', email: '[email protected]' },
{ id: 2, name: 'Bob', email: '[email protected]' },
{ id: 3, name: 'Charlie', email: '[email protected]' }
];
return (
<div>
<h2>User List</h2>
<ul>
{users.map((user) => (
<UserItem key={user.id} user={user} />
))}
</ul>
</div>
);
}Understanding Keys
Keys help React identify which items have changed, been added, or been removed in a list. They are essential for React’s reconciliation process and for optimizing rendering performance.
What are Keys?
Keys are special string attributes that need to be included when creating lists of elements:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);Best Practices for Keys
1. Use Stable Identifiers
Keys should be stable, predictable, and unique among siblings:
// Good - using unique IDs
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
// Bad - using array indices
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li>
))}
</ul>
);
}2. Don’t Use Random Values
Avoid using random values or timestamps as keys:
// Bad - random key changes on every render
function BadExample({ items }) {
return (
<ul>
{items.map((item) => (
<li key={Math.random()}>{item.name}</li>
))}
</ul>
);
}
// Good - using stable identifier
function GoodExample({ items }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}When to Use Array Indices
Array indices can be used as keys only when:
- The list is static and will never change
- The list has no unique identifiers
- The list will never be reordered or filtered
// Acceptable - static list
function StaticList() {
const items = ['First', 'Second', 'Third'];
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}Common List Patterns
1. Filtered Lists
Filter and render lists based on conditions:
function ActiveUsers({ users }) {
const activeUsers = users.filter(user => user.isActive);
return (
<div>
<h2>Active Users ({activeUsers.length})</h2>
<ul>
{activeUsers.map((user) => (
<li key={user.id}>
{user.name} - {user.status}
</li>
))}
</ul>
</div>
);
}2. Sorted Lists
Sort lists before rendering:
function SortedProducts({ products }) {
const sortedProducts = [...products].sort((a, b) =>
a.price - b.price
);
return (
<div>
<h2>Products (Low to High Price)</h2>
<div className="product-list">
{sortedProducts.map((product) => (
<div key={product.id} className="product">
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
))}
</div>
</div>
);
}3. Nested Lists
Handle lists within lists:
function Menu({ menuItems }) {
return (
<nav>
<ul>
{menuItems.map((item) => (
<li key={item.id}>
<a href={item.href}>{item.title}</a>
{item.submenu && item.submenu.length > 0 && (
<ul className="submenu">
{item.submenu.map((subItem) => (
<li key={subItem.id}>
<a href={subItem.href}>{subItem.title}</a>
</li>
))}
</ul>
)}
</li>
))}
</ul>
</nav>
);
}4. Paginated Lists
Implement pagination for large lists:
function PaginatedList({ items, itemsPerPage = 10 }) {
const [currentPage, setCurrentPage] = useState(1);
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = items.slice(indexOfFirstItem, indexOfLastItem);
const totalPages = Math.ceil(items.length / itemsPerPage);
const paginate = (pageNumber) => setCurrentPage(pageNumber);
return (
<div>
<div className="item-list">
{currentItems.map((item) => (
<div key={item.id} className="item">
{item.name}
</div>
))}
</div>
<div className="pagination">
<button
onClick={() => paginate(currentPage - 1)}
disabled={currentPage === 1}
>
Previous
</button>
<span>
Page {currentPage} of {totalPages}
</span>
<button
onClick={() => paginate(currentPage + 1)}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
</div>
);
}Interactive Lists
1. Adding and Removing Items
function DynamicList() {
const [items, setItems] = useState([
{ id: 1, text: 'Learn React' },
{ id: 2, text: 'Build projects' },
{ id: 3, text: 'Get hired' }
]);
const [inputValue, setInputValue] = useState('');
const addItem = () => {
if (inputValue.trim()) {
const newItem = {
id: Date.now(),
text: inputValue
};
setItems([...items, newItem]);
setInputValue('');
}
};
const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};
return (
<div>
<div className="add-item">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addItem()}
placeholder="Add new item"
/>
<button onClick={addItem}>Add</button>
</div>
<ul>
{items.map((item) => (
<li key={item.id}>
{item.text}
<button
onClick={() => removeItem(item.id)}
className="remove-btn"
>
Remove
</button>
</li>
))}
</ul>
</div>
);
}2. Reordering Lists
function ReorderableList() {
const [items, setItems] = useState([
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' },
{ id: 4, text: 'Item 4' }
]);
const moveItem = (index, direction) => {
const newItems = [...items];
const newIndex = direction === 'up' ? index - 1 : index + 1;
if (newIndex >= 0 && newIndex < items.length) {
[newItems[index], newItems[newIndex]] = [newItems[newIndex], newItems[index]];
setItems(newItems);
}
};
return (
<div>
<h2>Reorderable List</h2>
<ul>
{items.map((item, index) => (
<li key={item.id} className="reorderable-item">
<span>{item.text}</span>
<div className="controls">
<button
onClick={() => moveItem(index, 'up')}
disabled={index === 0}
>
↑
</button>
<button
onClick={() => moveItem(index, 'down')}
disabled={index === items.length - 1}
>
↓
</button>
</div>
</li>
))}
</ul>
</div>
);
}3. Search and Filter
function SearchableList({ items }) {
const [searchTerm, setSearchTerm] = useState('');
const [filterCategory, setFilterCategory] = useState('all');
const filteredItems = items.filter(item => {
const matchesSearch = item.name.toLowerCase().includes(searchTerm.toLowerCase());
const matchesCategory = filterCategory === 'all' || item.category === filterCategory;
return matchesSearch && matchesCategory;
});
const categories = ['all', ...new Set(items.map(item => item.category))];
return (
<div>
<div className="filters">
<input
type="text"
placeholder="Search items..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<select
value={filterCategory}
onChange={(e) => setFilterCategory(e.target.value)}
>
{categories.map(category => (
<option key={category} value={category}>
{category.charAt(0).toUpperCase() + category.slice(1)}
</option>
))}
</select>
</div>
<div className="results">
<p>Found {filteredItems.length} items</p>
<ul>
{filteredItems.map((item) => (
<li key={item.id}>
<strong>{item.name}</strong> - {item.category}
</li>
))}
</ul>
</div>
</div>
);
}Performance Optimization
1. React.memo for List Items
Prevent unnecessary re-renders of list items:
const UserItem = React.memo(({ user, onSelect }) => {
console.log(`Rendering user: ${user.name}`);
return (
<li onClick={() => onSelect(user)}>
{user.name} - {user.email}
</li>
);
});
function UserList({ users, onSelectUser }) {
return (
<ul>
{users.map((user) => (
<UserItem
key={user.id}
user={user}
onSelect={onSelectUser}
/>
))}
</ul>
);
}2. useMemo for Expensive Operations
Memoize expensive list operations:
function ExpensiveList({ items, filter }) {
const expensiveFilter = useMemo(() => {
console.log('Performing expensive filter operation...');
return items.filter(item => {
// Simulate expensive operation
return item.name.toLowerCase().includes(filter.toLowerCase()) &&
item.value > 100;
});
}, [items, filter]);
return (
<ul>
{expensiveFilter.map((item) => (
<li key={item.id}>
{item.name} - ${item.value}
</li>
))}
</ul>
);
}3. Virtual Scrolling
For very large lists, consider virtual scrolling:
import { FixedSizeList as List } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<List
height={500}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</List>
);
}Common Pitfalls and Solutions
1. Keys Not Being Unique
// Bad - duplicate keys
function BadExample() {
const items = [
{ id: 1, name: 'Item 1' },
{ id: 1, name: 'Item 2' }, // Duplicate ID!
{ id: 2, name: 'Item 3' }
];
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
// Good - ensure unique keys
function GoodExample() {
const items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
];
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}2. Keys Changing Between Renders
// Bad - key changes on every render
function BadExample({ items }) {
return (
<ul>
{items.map((item) => (
<li key={`${item.id}-${Date.now()}`}>
{item.name}
</li>
))}
</ul>
);
}
// Good - stable key
function GoodExample({ items }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name}
</li>
))}
</ul>
);
}3. Forgetting Keys in Nested Lists
// Bad - missing keys in nested list
function BadExample({ categories }) {
return (
<div>
{categories.map((category) => (
<div key={category.id}>
<h3>{category.name}</h3>
<ul>
{category.items.map((item) => (
<li>{item.name}</li> // Missing key!
))}
</ul>
</div>
))}
</div>
);
}
// Good - keys at all levels
function GoodExample({ categories }) {
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>
);
}Summary
Lists and keys are essential concepts in React development. Remember these key points:
- Use
map()to render lists of elements - Always provide unique and stable keys for list items
- Use meaningful identifiers (like IDs) rather than array indices
- Extract list items into separate components for better organization
- Optimize performance with React.memo and useMemo
- Handle common operations like filtering, sorting, and pagination
- Be careful with keys in nested lists and dynamic content
Mastering lists and keys will help you build efficient, maintainable React applications that can handle complex data display scenarios.
External Resources:
Related Tutorials: