Learn how to render arrays of data as lists in React using map(), understand the key prop and why it matters for performance and correctness.
In React, you frequently need to display a list of items — a list of users, products, messages, or any collection of data. The standard pattern is to use JavaScript's Array.map() method to transform an array of data into an array of JSX elements.
The map() method creates a new array by calling a function on every element of the original array. When you return JSX from that function, React renders each element:
function FruitList() {
const fruits = ['Apple', 'Banana', 'Cherry'];
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}Notice how the map() call is wrapped in curly braces {} inside the JSX. This is because JSX uses curly braces to embed JavaScript expressions. React can render arrays of elements directly, so map() returning an array of <li> elements works perfectly.
You can also map over arrays of objects:
function UserList() {
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
];
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}The pattern is always the same: take an array, call map(), return JSX for each item.
You may have noticed the key attribute on each list item. The key prop is a special attribute that React uses to identify which items in a list have changed, been added, or been removed.
When React re-renders a list, it needs to match elements from the previous render with elements from the new render. Without keys, React would have to compare elements purely by their position (index), which is slow and can cause bugs.
Rules for keys:
Keys must be unique among siblings. Each item in the same list must have a different key. Keys don't need to be globally unique — just unique within that specific list.
Keys must be stable. The same data item should always produce the same key, across re-renders.
Keys should not change. If a key changes between renders, React will destroy the old component and create a new one from scratch.
// Good: using a unique, stable id
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
// Bad: using Math.random() — changes every render!
{users.map((user) => (
<li key={Math.random()}>{user.name}</li>
))}What happens without keys? React will warn you in the console: Each child in a list should have a unique "key" prop. More importantly, without proper keys, React may incorrectly reuse DOM elements when items are reordered, leading to visual bugs — for example, input values appearing in the wrong list item.
Why not use the array index as a key? Using the index (key={index}) is a common fallback, but it can cause problems when items are reordered, inserted, or deleted. The index of an item changes when the list changes, which breaks React's ability to track which item is which. Use index keys only as a last resort when you have no stable identifier and the list is static (never reordered or filtered).
When your list items become complex, it is a good practice to extract each item into its own component. This keeps your code clean and makes each item easier to manage.
function TodoItem({ todo }) {
return (
<li style={{ color: todo.done ? 'gray' : 'black' }}>
{todo.done ? '✓ ' : '○ '}
{todo.text}
</li>
);
}
function TodoList() {
const todos = [
{ id: 1, text: 'Learn React', done: true },
{ id: 2, text: 'Build a project', done: false },
{ id: 3, text: 'Deploy to production', done: false },
];
return (
<ul>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}Important: The key prop is placed on the component in the map() call, not inside the component itself. React uses key internally and does not pass it as a prop to your component. If you need the id inside the component, pass it as a separate prop:
{todos.map((todo) => (
<TodoItem key={todo.id} id={todo.id} todo={todo} />
))}Extracting list items into components also makes it easier to add features like click handlers, hover effects, or animations to individual items without cluttering the parent component.
Choosing the right key is important. Here is a practical guide:
Best: Use a unique id from your data. Most real-world data — from databases, APIs, or user input — comes with a unique identifier. This is always the best choice for a key:
// Data from an API
const posts = [
{ id: 'abc123', title: 'First Post' },
{ id: 'def456', title: 'Second Post' },
];
posts.map((post) => <Article key={post.id} post={post} />);Acceptable: Use a unique, stable property.
If there is no id field but another property is unique and stable (like a username or email), you can use that:
users.map((user) => <UserCard key={user.email} user={user} />);Last resort: Use the index. Only use the array index when the list is static and will never be reordered, filtered, or have items added/removed:
// Only safe for purely static lists
const colors = ['Red', 'Green', 'Blue'];
colors.map((color, index) => <span key={index}>{color}</span>);Generate an id if needed. If you are creating data on the client (e.g., a todo app), generate a unique id when the item is created — not during rendering:
// Generate id when creating the item, not in map()
function addTodo(text) {
const newTodo = {
id: crypto.randomUUID(), // Generate once when created
text,
done: false,
};
setTodos([...todos, newTodo]);
}You can render nested lists by nesting map() calls. This is common when displaying hierarchical data:
function CategoryList() {
const categories = [
{
name: 'Fruits',
items: ['Apple', 'Banana', 'Cherry'],
},
{
name: 'Vegetables',
items: ['Carrot', 'Broccoli', 'Spinach'],
},
];
return (
<div>
{categories.map((category) => (
<div key={category.name}>
<h3>{category.name}</h3>
<ul>
{category.items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
))}
</div>
);
}Each level of nesting needs its own keys, but keys only need to be unique among their siblings — not across different lists.
You can also filter before mapping, to show only certain items:
function ActiveUsers({ users }) {
return (
<ul>
{users
.filter((user) => user.isActive)
.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}A common pattern is to chain filter() and map() together. You can also combine this with sort() to control the display order:
{users
.filter((u) => u.isActive)
.sort((a, b) => a.name.localeCompare(b.name))
.map((u) => <UserCard key={u.id} user={u} />)}<div id="root"></div>
<script src="https://unpkg.com/react@19/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
const { useState } = React;
function App() {
const [items, setItems] = useState([
{ id: 1, name: 'Learn HTML', done: true },
{ id: 2, name: 'Learn CSS', done: true },
{ id: 3, name: 'Learn JavaScript', done: true },
{ id: 4, name: 'Learn React', done: false },
]);
return (
<div>
<h2>Learning Path</h2>
<ul>
{items.map((item) => (
<li key={item.id} style={{
textDecoration: item.done ? 'line-through' : 'none',
color: item.done ? 'gray' : 'black',
}}>
{item.name}
</li>
))}
</ul>
<p>{items.filter((i) => i.done).length} of {items.length} completed</p>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>Why should you avoid using the array index as a key when list items can be reordered?