Volume I index
JSX Is Real React Code

Arrays in JSX

Ishtmeet Singh @ishtms/July 14, 2026/7 min read
#react#jsx#arrays#lists#map

A lesson list begins as data.

jsx
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.

jsx
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.

text
lesson object 1 -> li element 1
lesson object 2 -> li element 2
lesson object 3 -> li element 3

The original lessons array still contains objects. items contains React elements.

jsx
return <ul>{items}</ul>;

React accepts the element array as children of ul and continues through each element.

Mapping Inside JSX Uses the Same Operation

The intermediate variable is optional.

jsx
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.

jsx
lessons.map((lesson) => (
  <li key={lesson.id}>{lesson.title}</li>
));

Adding braces creates a block body. A block needs return.

jsx
lessons.map((lesson) => {
  return <li key={lesson.id}>{lesson.title}</li>;
});

This callback returns nothing.

jsx
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.

Give the List Valid Host Structure

Map lessons to li elements when the parent is a list.

jsx
<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.

jsx
<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.

jsx
<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.

jsx
function LessonItem({ lesson }) {
  return (
    <li>
      <h2>{lesson.title}</h2>
      <p>{lesson.minutes} min</p>
    </li>
  );
}

The list maps data to component elements.

jsx
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.

jsx
const publishedLessons = lessons.filter(
  (lesson) => lesson.published,
);

Map the selected array.

jsx
const items = publishedLessons.map((lesson) => (
  <LessonItem key={lesson.id} lesson={lesson} />
));

Each method has one result.

text
filter -> lesson objects that pass the test
map    -> React elements for those objects

The operations can be chained.

jsx
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.

jsx
const visibleLessons = lessons.filter(matchesCurrentFilter);
const visibleCount = visibleLessons.length;
jsx
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.

jsx
<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.

jsx
[].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.

jsx
if (visibleLessons.length === 0) {
  return <p>No lessons match this filter.</p>;
}

Then render the populated result.

jsx
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.

Do Not Mutate Props During Render

sort changes the array it is called on.

jsx
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.

jsx
const sortedLessons = lessons.toSorted(
  (a, b) => a.title.localeCompare(b.title),
);

For a compatibility target without toSorted, copy before using sort.

jsx
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.

text
filter
map
toSorted
toReversed

Methods such as sort, reverse, splice, push, and pop mutate their array receiver.

Avoid Generating Data Inside the Mapping Result

This code creates another ID every time the list renders.

jsx
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.

jsx
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.

jsx
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.

jsx
const visibleLessons = lessons
  .filter((lesson) => lesson.published)
  .toSorted((a, b) => a.order - b.order);

Handle the empty result.

jsx
if (visibleLessons.length === 0) {
  return <p>No published lessons.</p>;
}

Map records to components.

jsx
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.

text
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 result

Source Notes