How to Loop Over an Array or Array of Objects in React
Advertisement
🧩 Scenario
Architecture Walkthrough
map() as the Primary List Rendering Tool
React renders lists by returning an array of JSX elements. map() transforms each item in a data array into a JSX element, returning a new array that React renders in order. Because map() is an expression that produces a value, it can be used directly inside JSX curly braces, unlike for or forEach which are statements or return undefined.
Every element returned from map() must have a key prop. The key must be stable (not Math.random()) and unique among siblings (not necessarily globally unique). A stable ID from the data source is the correct choice; array index is only acceptable for static, append-only lists.
Combining filter() and map()
For conditional lists (show only in-stock items, show only admin users), chain filter() before map(). filter() returns a new array of only the matching items; map() transforms them. The chain reads naturally: filter what you want, then render each result.
Always handle the empty array case: an empty list renders nothing but gives the user no feedback. Use a ternary or an early return to show an empty state message when no items remain after filtering.
Nested Arrays with flatMap()
For two-dimensional data (categories with items, threads with replies), use flatMap() to flatten and map in one pass instead of nested map() calls. Nested map() returns an array of arrays, which React can render but which makes the JSX harder to read and requires keys at two levels.
Key Code Explained
// Basic: array of strings
function TagList({ tags }: { tags: string[] }) {
if (tags.length === 0) return <p className="empty">No tags added.</p>;
return (
<ul>
{tags.map((tag) => (
<li key={tag} className="tag">
{tag}
</li>
))}
</ul>
);
}
// Array of objects with stable IDs
interface Product {
id: string;
name: string;
price: number;
inStock: boolean;
}
function ProductList({ products }: { products: Product[] }) {
if (products.length === 0) {
return <p className="empty">No products found.</p>;
}
return (
<ul className="product-grid">
{products.map((product) => (
<li key={product.id} className="product-card">
<h3>{product.name}</h3>
<p>${product.price.toFixed(2)}</p>
{!product.inStock && <span className="badge">Out of stock</span>}
</li>
))}
</ul>
);
}
// filter() + map(): render only in-stock products
function InStockProducts({ products }: { products: Product[] }) {
const available = products.filter((p) => p.inStock);
if (available.length === 0) {
return <p className="empty">All products are currently out of stock.</p>;
}
return (
<ul>
{available.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
// flatMap(): flatten nested arrays in one pass
interface Category {
id: string;
name: string;
items: { id: string; label: string }[];
}
function FlatItemList({ categories }: { categories: Category[] }) {
const allItems = categories.flatMap((cat) =>
cat.items.map((item) => ({ ...item, categoryId: cat.id })),
);
return (
<ul>
{allItems.map((item) => (
// key combines category and item IDs to ensure uniqueness
<li key={`${item.categoryId}-${item.id}`}>{item.label}</li>
))}
</ul>
);
}
// Common mistake: forgetting return in {} callback
// BAD: returns undefined for every item, renders nothing
{items.map((item) => {
<li key={item.id}>{item.name}</li> // missing return
})}
// GOOD: implicit return with ()
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
// GOOD: explicit return with {}
{items.map((item) => {
return <li key={item.id}>{item.name}</li>;
})}
The flatMap example creates a composite key by joining categoryId and item.id. This is necessary when item IDs are unique only within a category but might repeat across categories. Composite keys keep sibling uniqueness without requiring globally unique IDs.
Tradeoffs
| Method | Returns | Use for |
|---|---|---|
| map() | Transformed array | Rendering every item in an array |
| filter() + map() | Filtered, then transformed | Rendering a subset that matches a condition |
| flatMap() | Flattened array | Rendering all items across nested arrays |
| forEach() | undefined | Side effects only — never use for rendering |
What Interviewers Actually Check
- Whether you use
map()and can explain why (expression vs statement) - Whether you use a stable ID as the key, not index
- Whether you handle the empty array case with a fallback UI
- Whether you know
filter().map()for conditional lists - Whether you know
forEach()returns undefined and cannot be used for rendering
Follow-Up Questions
- How would you virtualize a list of 10,000 items using
react-windoworreact-virtualto avoid rendering all DOM nodes at once? - How does
React.Fragmenthelp whenmap()needs to return multiple sibling elements per item without a wrapper div? - How would you implement infinite scroll pagination that appends more items to the same list on scroll?
- How does sorting a list in React differ from sorting in a database query, and what are the performance implications?
- How would you memoize list items with
React.memoto prevent re-rendering unchanged items when the parent updates?
Common Candidate Mistakes
- Using
forEach()instead ofmap()and getting nothing rendered (forEach returns undefined) - Using array block syntax
map((item) => { <li /> })without areturnand getting nothing rendered - Using array index as a key on a list that can be filtered or sorted
- Not handling the empty state, leaving a blank section with no user feedback
- Using
Math.random()as a key thinking it avoids duplicates, not knowing it forces all items to remount every render
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you render a list of items using
map()with a stable unique key? - Can you render a filtered subset using
filter().map()with an empty state fallback? - Can you render an array of objects and access each field inside the JSX?
- Can you explain why
map()works in JSX butforloops do not? - Can you explain why
forEach()cannot be used for rendering?
Summary
React renders lists by returning arrays of JSX elements. map() is the primary tool because it is an expression that returns a new array, making it directly usable inside JSX curly braces. For and forEach are statements or return undefined and cannot be used inline in JSX.
Every element from map() needs a key prop that is stable and unique among siblings. A unique ID from the data source is correct; array index causes bugs when the list can reorder or filter. Always handle the empty array case: a list that renders nothing without feedback leaves users confused about whether something went wrong or the list is intentionally empty.
For conditional lists, chain filter() before map() to reduce the array first, then transform the results. For nested arrays (categories with items), use flatMap() to flatten and map in a single pass, avoiding nested map() calls that produce arrays of arrays and require keys at multiple levels.
Why use map() instead of a for loop for rendering lists?
map() is an expression that returns a new array of JSX elements, which can be embedded directly in JSX. A for loop is a statement and cannot be used inside JSX curly braces.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement