Arrays in JSX
A lesson list begins as data.
const lessons = [
{ id: "expressions", title: "Expressions Inside JSX" },
{ id: "attributes", title: "Attributes and Styles" },
{ id: "fragments", title: "Fragments and Nested Trees" },
];The array contains three objects. React cannot render those objects directly as children. JavaScript must transform each object into a React node that represents one list item.
map performs that one-to-one transformation.
Read map as a Data Operation
Call map on the array.
const items = lessons.map((lesson) => (
<li key={lesson.id}>{lesson.title}</li>
));map calls the provided function once for each lesson. The callback receives the current lesson object and returns one li element. map collects those returned elements into a new array.
lesson object 1 -> li element 1
lesson object 2 -> li element 2
lesson object 3 -> li element 3The original lessons array still contains objects. items contains React elements.
return <ul>{items}</ul>;React accepts the element array as children of ul and continues through each element.
map transforms the data array. React renders the array returned by that transformation.
Mapping Inside JSX Uses the Same Operation
The intermediate variable is optional.
return (
<ul>
{lessons.map((lesson) => (
<li key={lesson.id}>{lesson.title}</li>
))}
</ul>
);The braces enter a JavaScript expression position. The map call produces the element array for that position.
Use an intermediate variable when the transformation needs filtering, sorting, debugging, or an empty check. Use the inline form when one short mapping remains clear.
Do not treat inline code as inherently more advanced. Both forms call the same array method during rendering.
The Callback Must Return a Node
An arrow callback with parentheses returns its expression.
lessons.map((lesson) => (
<li key={lesson.id}>{lesson.title}</li>
));Adding braces creates a block body. A block needs return.
lessons.map((lesson) => {
return <li key={lesson.id}>{lesson.title}</li>;
});This callback returns nothing.
lessons.map((lesson) => {
<li key={lesson.id}>{lesson.title}</li>;
});Each call produces undefined, so map returns an array of undefined values. React renders no list items for those children.
The JSX expression itself is valid and can be created inside the callback. The missing return discards it before map receives it.
Arrow-function braces start a block. Add an explicit return for the element result.
Give the List Valid Host Structure
Map lessons to li elements when the parent is a list.
<ul>
{lessons.map((lesson) => (
<li key={lesson.id}>{lesson.title}</li>
))}
</ul>This source maps directly to valid list structure.
Do not map to bare article elements inside ul.
<ul>
{lessons.map((lesson) => (
<article key={lesson.id}>{lesson.title}</article>
))}
</ul>Wrap the richer content in li or use a non-list parent when the content is not a semantic list.
<li key={lesson.id}>
<article>{lesson.title}</article>
</li>Arrays change how siblings are produced, not the browser's HTML nesting rules.
Map to a Component for a Named Item
Extract one item when it has its own component responsibility.
function LessonItem({ lesson }) {
return (
<li>
<h2>{lesson.title}</h2>
<p>{lesson.minutes} min</p>
</li>
);
}The list maps data to component elements.
function LessonList({ lessons }) {
return (
<ul>
{lessons.map((lesson) => (
<LessonItem key={lesson.id} lesson={lesson} />
))}
</ul>
);
}The key belongs on LessonItem because those elements are the siblings directly returned by map. React compares keys among siblings when matching one result with the next.
LessonItem itself returns li, preserving valid host structure.
Do not extract a component only to move one line away from map. Extract after the item has a useful name, markup responsibility, or interaction behavior.
Filter Data Before Mapping
filter selects items and returns a new array.
const publishedLessons = lessons.filter(
(lesson) => lesson.published,
);Map the selected array.
const items = publishedLessons.map((lesson) => (
<LessonItem key={lesson.id} lesson={lesson} />
));Each method has one result.
filter -> lesson objects that pass the test
map -> React elements for those objectsThe operations can be chained.
const items = lessons
.filter((lesson) => lesson.published)
.map((lesson) => (
<LessonItem key={lesson.id} lesson={lesson} />
));Keep separate variables when the selected data is also needed for a count or empty state.
Derive Counts from the Selected Array
The number shown to the reader should describe the list actually rendered.
const visibleLessons = lessons.filter(matchesCurrentFilter);
const visibleCount = visibleLessons.length;return (
<section>
<p>{visibleCount} lessons</p>
<LessonList lessons={visibleLessons} />
</section>
);Do not filter once for the list and calculate the count from the unfiltered source.
<p>{lessons.length} lessons</p>That can report six while the current filter displays two. One derived array should supply both the rendered items and any count that describes them.
Handle an Empty Array Explicitly
Mapping an empty array returns another empty array.
[].map((lesson) => <LessonItem lesson={lesson} />)React renders no items. A bare empty ul can leave the reader without an explanation.
Check the selected data before returning the list.
if (visibleLessons.length === 0) {
return <p>No lessons match this filter.</p>;
}Then render the populated result.
return <LessonList lessons={visibleLessons} />;Render the empty message in the component that knows why the array is empty. A generic LessonList may instead support an explicit emptyMessage prop when several parents need consistent list handling.
An empty array is valid data. Add empty-state UI when blank output would not explain the result.
Do Not Mutate Props During Render
sort changes the array it is called on.
function LessonList({ lessons }) {
lessons.sort((a, b) => a.title.localeCompare(b.title));
// ...
}The parent and other components can hold the same array reference. Sorting it inside the child changes their data order during rendering.
Use the non-mutating toSorted method in current environments.
const sortedLessons = lessons.toSorted(
(a, b) => a.title.localeCompare(b.title),
);For a compatibility target without toSorted, copy before using sort.
const sortedLessons = [...lessons].sort(
(a, b) => a.title.localeCompare(b.title),
);The array copy is shallow. The lesson objects remain shared references. Sorting the copy is safe for array order, but mutating sortedLessons[0].title would still mutate the original object.
Use non-mutating operations for the entire calculation.
filter
map
toSorted
toReversedMethods such as sort, reverse, splice, push, and pop mutate their array receiver.
Copying an array protects the collection order, not nested object properties.
Avoid Generating Data Inside the Mapping Result
This code creates another ID every time the list renders.
lessons.map((lesson) => (
<LessonItem
key={crypto.randomUUID()}
lesson={lesson}
/>
));Create IDs when records are created or received, then retain them in the data. Generating another ID during rendering makes the same record look new every time.
const newLesson = {
id: crypto.randomUUID(),
title: "New lesson",
};The mapping operation should transform existing data into UI. It should not modify the records or invent a new identity for the same record on every render.
Arrays Can Contain Other Renderable Nodes
React can render an array of strings and elements.
const content = [
"Chapter 3",
<strong key="title">JSX</strong>,
];Mixed child arrays are supported, but normal application lists are easier to reason about when each item follows one data and element contract.
Nested arrays are flattened into the child sequence during React rendering. Avoid using that behavior to hide a poorly modeled collection. Flatten or group the source data explicitly so list identity and host structure remain visible.
Build the Visible Lesson List
Select and order data before JSX.
const visibleLessons = lessons
.filter((lesson) => lesson.published)
.toSorted((a, b) => a.order - b.order);Handle the empty result.
if (visibleLessons.length === 0) {
return <p>No published lessons.</p>;
}Map records to components.
return (
<ul>
{visibleLessons.map((lesson) => (
<LessonItem key={lesson.id} lesson={lesson} />
))}
</ul>
);The source data remains unchanged. The final JSX displays the selected order.
Lesson Check
The list operation now has distinct stages.
source array contains data records
filter selects records
non-mutating order produces another array
map returns one React node for each record
React renders the node array as siblings
empty data receives an explicit resultSource Notes
- React documentation, Rendering Lists
- MDN,
Array.prototype.map() - MDN,
Array.prototype.filter() - MDN,
Array.prototype.toSorted()