1. Introduction π
Displaying collections of data β user lists, product grids, comment threads β is one of the most common tasks in web development. React makes this straightforward by leveraging plain JavaScript array methods combined with JSX. This tutorial covers everything from basic list rendering to advanced techniques like virtualization and infinite scrolling.
Information
2. What is List Rendering? π€
List rendering is the process of transforming an array of data into an array of JSX elements, one for each item, so React can display them on screen.
Code Snippet
const fruits = ["Apple", "Banana", "Cherry"];
function FruitList() {
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}3. Why Render Lists? π‘
- Dynamic Data: Most real applications display data fetched from APIs or databases.
- Reusable Markup: Avoid repeating near-identical JSX for each item manually.
- Scalability: The same rendering logic works whether there are 3 items or 3,000.
4. Rendering Arrays π¦
Since JSX can render arrays of elements directly, transforming a data array into an array of JSX elements is all that's needed to render a list.
Code Snippet
const numbers = [1, 2, 3, 4, 5];
function NumberList() {
const listItems = numbers.map((number) => <li key={number}>{number}</li>);
return <ul>{listItems}</ul>;
}5. Using map() πΊοΈ
Array.prototype.map() is the standard way to transform data into JSX elements, since it returns a new array without mutating the original.
Code Snippet
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
function UserList() {
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}Tip
6. Rendering Objects π§Ύ
Since JSX cannot render plain JavaScript objects directly, you must extract and render their individual properties explicitly.
Code Snippet
const product = { id: 1, name: "Laptop", price: 999 };
function ProductCard({ product }) {
return (
<div>
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
);
}Danger
7. Rendering Nested Lists πͺ
Data structures with nested arrays require nested map() calls, with a unique key at each level.
Code Snippet
const categories = [
{ id: "c1", name: "Fruits", items: ["Apple", "Banana"] },
{ id: "c2", name: "Vegetables", items: ["Carrot", "Potato"] },
];
function CategoryList() {
return (
<div>
{categories.map((category) => (
<div key={category.id}>
<h3>{category.name}</h3>
<ul>
{category.items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
))}
</div>
);
}8. Rendering Components in Lists π§©
Instead of rendering raw JSX elements inline, list items are often rendered as separate, reusable components β improving readability and encapsulation.
Code Snippet
function ProductCard({ product }) {
return (
<div className="card">
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
);
}
function ProductGrid({ products }) {
return (
<div className="grid">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}Important
9. Understanding Keys π
A key is a special prop that helps React identify which items have changed, been added, or been removed during re-renders, enabling efficient and correct updates.
Code Snippet
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}Note
10. Choosing Good Keys β
- Use a stable, unique identifier from your data, such as a database ID.
- Keys only need to be unique among siblings, not globally across the entire app.
- Avoid generating keys with Math.random() or similar β they change on every render, defeating their purpose.
Code Snippet
// β
Good β stable, unique ID from data
{users.map((user) => <li key={user.id}>{user.name}</li>)}
// β Bad β regenerates a new key every render
{users.map((user) => <li key={Math.random()}>{user.name}</li>)}11. Unique Keys π
When your data lacks a natural unique identifier, generate one when the data is created β for example, using a UUID β rather than relying on unstable values at render time.
Code Snippet
import { v4 as uuidv4 } from 'uuid';
function addTodo(text) {
return { id: uuidv4(), text, completed: false };
}Tip
12. Index as a Key β οΈ
Using the array index as a key is acceptable only when the list is static β never reordered, filtered, or modified.
Code Snippet
// Acceptable only for static, never-reordered lists
{staticLabels.map((label, index) => (
<span key={index}>{label}</span>
))}Danger
13. Keyed Fragments π§©
When a list item needs to render multiple sibling elements without an extra wrapper <div>, use the explicit Fragment form with a key, since the shorthand <> syntax doesn't accept props.
Code Snippet
import { Fragment } from 'react';
function DefinitionList({ terms }) {
return (
<dl>
{terms.map((term) => (
<Fragment key={term.id}>
<dt>{term.word}</dt>
<dd>{term.definition}</dd>
</Fragment>
))}
</dl>
);
}14. Dynamic Lists π
Lists backed by state can grow, shrink, and reorder in response to user actions, always producing a new array via immutable updates.
Code Snippet
function TodoList() {
const [todos, setTodos] = useState([]);
function addTodo(text) {
setTodos([...todos, { id: crypto.randomUUID(), text }]);
}
function removeTodo(id) {
setTodos(todos.filter((todo) => todo.id !== id));
}
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>Remove</button>
</li>
))}
</ul>
);
}15. Conditional List Rendering π
Combine list rendering with conditional logic to show alternate content, such as an empty state, when there's no data to display.
Code Snippet
function CommentList({ comments }) {
if (comments.length === 0) {
return <p>No comments yet. Be the first! β¨</p>;
}
return (
<ul>
{comments.map((comment) => (
<li key={comment.id}>{comment.text}</li>
))}
</ul>
);
}16. Filtering Lists π
Use Array.prototype.filter() before map() to render only the items matching a specific condition.
Code Snippet
function ActiveUserList({ users }) {
const activeUsers = users.filter((user) => user.isActive);
return (
<ul>
{activeUsers.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}Tip
17. Sorting Lists π
Use Array.prototype.toSorted() (or spread into a new array before sort()) to order list items without mutating the original array.
Code Snippet
function SortedProductList({ products }) {
const sortedProducts = [...products].sort((a, b) => a.price - b.price);
return (
<ul>
{sortedProducts.map((product) => (
<li key={product.id}>{product.name} β ${product.price}</li>
))}
</ul>
);
}Danger
18. Grouping Lists ποΈ
Use Array.prototype.reduce() to group flat data into categories before rendering.
Code Snippet
function groupByCategory(products) {
return products.reduce((groups, product) => {
const key = product.category;
if (!groups[key]) groups[key] = [];
groups[key].push(product);
return groups;
}, {});
}
function GroupedProductList({ products }) {
const grouped = groupByCategory(products);
return (
<div>
{Object.entries(grouped).map(([category, items]) => (
<div key={category}>
<h3>{category}</h3>
<ul>
{items.map((item) => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
))}
</div>
);
}19. Flattening Lists π
Use Array.prototype.flat() or flatMap() to render a single-level list from nested array structures.
Code Snippet
const nestedTags = [["react", "jsx"], ["css", "html"]];
const flatTags = nestedTags.flat();
function TagList() {
return (
<div>
{flatTags.map((tag) => (
<span key={tag} className="tag">{tag}</span>
))}
</div>
);
}20. Empty Lists π
Code Snippet
function TaskList({ tasks }) {
if (tasks.length === 0) {
return (
<div className="empty-state">
<p>π No tasks remaining!</p>
</div>
);
}
return (
<ul>
{tasks.map((task) => <li key={task.id}>{task.title}</li>)}
</ul>
);
}Best Practice
21. Loading Lists β³
Code Snippet
function ProductList({ products, isLoading }) {
if (isLoading) {
return <p>Loading products... β³</p>;
}
return (
<ul>
{products.map((product) => <li key={product.id}>{product.name}</li>)}
</ul>
);
}Tip
22. Paginated Lists π
Pagination splits a large dataset into discrete pages, reducing the amount of data rendered and fetched at once.
Code Snippet
function PaginatedList({ items, pageSize = 10 }) {
const [page, setPage] = useState(1);
const start = (page - 1) * pageSize;
const visibleItems = items.slice(start, start + pageSize);
const totalPages = Math.ceil(items.length / pageSize);
return (
<div>
<ul>
{visibleItems.map((item) => <li key={item.id}>{item.name}</li>)}
</ul>
<button disabled={page === 1} onClick={() => setPage(page - 1)}>Previous</button>
<span> Page {page} of {totalPages} </span>
<button disabled={page === totalPages} onClick={() => setPage(page + 1)}>Next</button>
</div>
);
}23. Infinite Scrolling Lists βΎοΈ
Infinite scrolling loads more items automatically as the user approaches the bottom of the list, commonly implemented using the IntersectionObserver API.
Code Snippet
function InfiniteList({ items, loadMore, hasMore }) {
const observerRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
loadMore();
}
});
if (observerRef.current) observer.observe(observerRef.current);
return () => observer.disconnect();
}, [hasMore, loadMore]);
return (
<div>
<ul>
{items.map((item) => <li key={item.id}>{item.name}</li>)}
</ul>
<div ref={observerRef}>{hasMore && "Loading more..."}</div>
</div>
);
}24. Virtualized Lists π
List virtualization renders only the items currently visible in the viewport, dramatically improving performance for lists with thousands of items.
Code Snippet
import { FixedSizeList } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>{items[index].name}</div>
);
return (
<FixedSizeList height={400} itemCount={items.length} itemSize={35} width="100%">
{Row}
</FixedSizeList>
);
}Best Practice
25. Performance Optimization β‘
- Always use stable, unique keys to help React skip unnecessary re-renders of unchanged items.
- Wrap list item components in memo to avoid re-rendering items whose props haven't changed.
- Use useMemo to avoid recomputing expensive filtering or sorting on every render.
- Consider virtualization for very large lists instead of rendering every item at once.
Code Snippet
const sortedItems = useMemo(
() => [...items].sort((a, b) => a.price - b.price),
[items]
);26. Best Practices π
- Always provide a stable, unique key for each list item.
- Avoid using array index as a key for dynamic, reorderable lists.
- Filter, sort, and transform data immutably before mapping to JSX.
- Handle loading and empty states explicitly for every list.
- Extract complex list items into their own reusable components.
- Consider pagination or virtualization for large datasets.
27. Common Mistakes π«
- Forgetting the key prop entirely, resulting in React console warnings and reconciliation bugs.
- Using array index as a key on lists that get reordered or filtered.
- Mutating the original array with sort(), splice(), or push() instead of creating a copy.
- Rendering raw objects directly inside JSX instead of their individual properties.
- Rendering thousands of items without pagination or virtualization, hurting performance.
Danger
28. Frequently Asked Questions β
Question
Answer
Question
Answer
Question
Answer
29. Summary π
Rendering lists in React relies on standard JavaScript array methods like map(), filter(), and sort(), combined with the crucial key prop for efficient, correct updates. From simple static lists to paginated, infinite-scrolling, or virtualized collections, the same core principles of immutability and stable keys apply throughout.