Question

Rendering Repeated Components in React JSX with map and Array.from

javascriptreactjsjsx

Question

How can I render the <ObjectRow /> component multiple times inside a React <tbody>? I would like to produce a row for each value from 0 up to numrows - 1, similar to this template-style pseudocode:

<tbody>
  for (let i = 0; i < numrows; i++) {
    <ObjectRow />
  }
</tbody>

I understand that a for statement cannot be written directly inside JSX. What is the React JSX approach for adding the same component multiple times?

Short Answer

You will learn how React renders lists of components, why statements such as for cannot appear directly inside JSX, and how to create repeated elements with Array.from() or map(). You will also learn why each rendered row needs a stable key.

Concept

JSX describes the UI that a React component should return. Inside JSX braces ({}), you can place JavaScript expressions: values that evaluate to something React can render.

A for loop is a statement, not an expression. It performs work but does not itself produce an array of elements for JSX to render. That is why this is invalid:

<tbody>
  {for (let i = 0; i < 3; i++) {}}
</tbody>

React can render an array of elements. Therefore, the common solution is to create an array and transform each item into a component using map().

When you only have a number such as numrows, Array.from() can first create an array of that length. Then map() creates one <ObjectRow /> per index.

Every item in a rendered list needs a key prop. A key lets React identify which rendered item corresponds to which logical item between updates. Stable keys help React update lists correctly and efficiently.

For a fixed number of purely visual rows, an index can be acceptable as a key. For rows backed by real data that can be reordered, inserted, or removed, use a stable ID from that data instead.

Mental Model

Think of JSX as an order form that must contain completed items, not instructions for making them.

  • A for loop is an instruction: “repeat this action.”
  • An array of <ObjectRow /> elements is the completed set of items: “here are the rows to display.”
  • map() is the assembly line that turns each input item into one UI item.
  • A key is each row's tracking label, allowing React to recognize it during later updates.

JSX can receive the finished array, so React can render every row in it.

Take Quiz

Syntax and Examples

Create a sequence from a count with Array.from(), then map each index to a component:

function ObjectTable({ numrows }) {
  return (
    <table>
      <tbody>
        {Array.from({ length: numrows }, (_, index) => (
          <ObjectRow key={index} rowNumber={index} />
        ))}
      </tbody>
    </table>
  );
}

Array.from({ length: numrows }, ...) creates an array with numrows positions. Its callback runs once for each position:

  • index starts at 0.
  • The callback returns an <ObjectRow /> element.
  • React renders the resulting array inside <tbody>.
  • key={index} gives each generated element a key.

A table row component should return a <tr>, because it is being placed directly inside a <tbody>:

function ObjectRow({ rowNumber }) {
  return (
    <tr>
      <td>Row {rowNumber + 1}</td>
    </tr>
  );
}

When you already have data, map over the data directly. This is usually the best pattern:

function ObjectTable({ objects }) {
  return (
    <table>
      <tbody>
        {objects.map((object) => (
          <ObjectRow key={object.id} object={object} />
        ))}
      </tbody>
    </table>
  );
}

Here, object.id is preferable to an array index because it identifies the actual object.

Step by Step Execution

Consider this component:

function ObjectTable() {
  const numrows = 3;

  const rows = Array.from({ length: numrows }, (_, index) => (
    <ObjectRow key={index} rowNumber={index + 1} />
  ));

  return <tbody>{rows}</tbody>;
}

Execution proceeds as follows:

  1. numrows is set to 3.

  2. Array.from({ length: 3 }, callback) creates three positions.

  3. The callback runs for index 0 and returns:

    <ObjectRow key={0} rowNumber={1} />
    
  4. It runs again for index 1, returning a row with key={1} and rowNumber={2}.

  5. It runs one final time for index 2, returning a row with key={2} and rowNumber={3}.

  6. rows is now an array containing three React elements.

  7. {rows} inserts that array into <tbody>, and React renders all three rows.

The same idea can be written inline when it remains readable:

<tbody>
  {Array.from({ length: 3 }, (_, index) => (
    <ObjectRow key={index} rowNumber={index + 1} />
  ))}
</tbody>

Real World Use Cases

Repeated component rendering is used throughout React applications:

  • Database tables: Render one <UserRow /> for every user returned by an API.
  • Product listings: Render one <ProductCard /> for every product in a search result.
  • Navigation menus: Render links from a configuration array.
  • Form fields: Render repeated input groups, such as addresses or invoice line items.
  • Loading placeholders: Render a known number of skeleton rows while data is loading.
  • Calendar views: Render days, events, or time slots generated from date data.

For example, skeleton table rows can be count-based:

<tbody>
  {isLoading
    ? Array.from({ length: 5 }, (_, index) => (
        <SkeletonRow key={index} />
      ))
    : users.map((user) => (
        <UserRow key={user.id} user={user} />
      ))}
</tbody>
Take Quiz

Real Codebase Usage

In real codebases, developers generally make rendering data-driven. Rather than separately storing a count and trying to repeat markup, they store an array of records and map it to components.

function OrdersTable({ orders }) {
  if (orders.length === 0) {
    return <p>No orders found.</p>;
  }

  return (
    <table>
      <tbody>
        {orders.map((order) => (
          <OrderRow key={order.id} order={order} />
        ))}
      </tbody>
    </table>
  );
}

Common patterns include:

  • Guard clauses: Return an empty state or loading state before rendering the main list.
  • Filtering first: Use filter() before map() when only some records should appear.
  • Sorting first: Create a sorted copy of data before mapping if the display order matters.
  • Small row components: Keep table-cell markup inside OrderRow, while the parent owns list rendering.
  • Stable IDs as keys: Use IDs from a database, API, or locally created record.
  • Count-based rendering: Use Array.from() for placeholders, pagination buttons, rating stars, or other generated UI without source records.

Example of filtering before rendering:

const activeUsers = users.filter((user) => user.active);

return (
  <tbody>
    {activeUsers.map((user) => (
      <UserRow key={user.id} user={user} />
    ))}
  </tbody>
);

Common Mistakes

Writing a for loop directly in JSX

This is invalid because for is a statement:

<tbody>
  for (let i = 0; i < numrows; i++) {
    <ObjectRow />
  }
</tbody>

Create the array before return, or use an expression such as map() inside braces.

Forgetting braces around JavaScript

Without braces, JSX treats the text as content rather than JavaScript:

// Incorrect
<tbody>
  Array.from({ length: 3 })
</tbody>

Use braces:

<tbody>{Array.from({ length: 3 })}</tbody>

In practice, also map those positions to elements.

Forgetting the key prop

React will warn when list items have no keys:

{users.map((user) => (
  <UserRow user={user} />
))}

Use a stable identifier:

{users.map((user) => (
  <UserRow key={user.id} user={user} />
))}

Using an index key for a changing data list

This can cause a row's internal state to appear on the wrong item after insertion, deletion, or reordering:

{users.map((user, index) => (
  <UserRow key={index} user={user} />
))}

Prefer key={user.id} when possible. Index keys are most suitable for fixed, non-reordered generated UI such as five loading placeholders.

Returning the wrong element type inside a table

A <tbody> should contain <tr> elements. Ensure ObjectRow returns a table row:

function ObjectRow() {
  return <tr><td>Valid table row</td></tr>;
}

Do not return a <div> directly from a component rendered within <tbody>.

Calling map() on a number

Numbers do not have a map() method:

// Incorrect
numrows.map(() => <ObjectRow />);

Use Array.from({ length: numrows }, ...), or map over an actual data array.

Comparisons

ApproachBest forExample
array.map()Rendering records you already haveusers.map(user => <UserRow ... />)
Array.from({ length })Rendering a known count with no data arraySkeleton rows, stars, page buttons
for loop before returnMore complex generation logicBuild rows, then render {rows}
forEach()Side effects, not creating rendered outputLogging or updating external values

map() and forEach() are often confused:

// map returns a new array, so React can render it.
const rows = users.map((user) => <UserRow key={user.id} user={user} />);

// forEach returns undefined, so it is not appropriate here.
const rows = users.forEach((user) => <UserRow key={user.id} user={user} />);

You can use a traditional loop outside JSX when needed:

const rows = [];

for (let index = 0; index < numrows; index += 1) {
  rows.push(<ObjectRow key={index} rowNumber={index + 1} />);
}

return <tbody>{rows}</tbody>;

This works, but Array.from() or map() is often shorter and easier to read for simple repeated rendering.

Take Quiz

Cheat Sheet

// Render from existing data: preferred for application data
items.map((item) => <Item key={item.id} item={item} />)

// Render a component a fixed number of times
Array.from({ length: count }, (_, index) => (
  <Item key={index} number={index + 1} />
))

// Prepare elements before return
const elements = [];
for (let index = 0; index < count; index += 1) {
  elements.push(<Item key={index} />);
}
return <section>{elements}</section>;
  • Put JavaScript expressions in JSX with {...}.
  • A for loop cannot be placed directly in JSX.
  • map() returns a new array; React can render that array.
  • Give each sibling created in a list a key.
  • Use stable data IDs for keys when list data can change order.
  • An index key is reasonable only for stable, generated items with no identity.
  • A component rendered within <tbody> should return a <tr>.

FAQ

How do I loop through a number in React JSX?

Use Array.from({ length: count }, callback). The callback can return one React element for each index.

{Array.from({ length: count }, (_, index) => <Item key={index} />)}

Can I use a for loop in a React component?

Yes. Use it in JavaScript before the return statement to build an array of elements, then render that array with {elements}. You cannot write the loop directly as JSX content.

Why does React require a key when using map()?

Keys let React match each list item with its previous rendered version. This matters when items are added, removed, or reordered.

Is key={index} always wrong in React?

No. It is acceptable for static generated items, such as loading placeholders. Avoid it for data lists whose order or length can change; use a stable item ID instead.

Why does my table show invalid DOM warnings?

A <tbody> expects <tr> children. Ensure the component you render inside it returns a <tr>, and place cells inside <td> or <th> elements.

Should I use map() or Array.from()?

Use map() when you have an array of data. Use Array.from() when you only have a count and need to generate positions to render.

Can a React component return an array of elements?

Yes. React can render arrays of elements. Each sibling element in that array should have a unique key.

Take Quiz

Mini Project

Description

Build a small score table that generates a requested number of table rows. Each row displays a row number and an editable default score. This demonstrates count-based component rendering, passing props, and correct table markup.

Goal

Render a <ScoreRow /> component once for every row requested by the user.

Requirements

Create a ScoreRow component that returns a <tr> element. Render rows inside a <tbody> using Array.from(). Give every generated row a key prop. Pass the displayed row number as a prop. Allow the user to choose a row count between 1 and 10.

Take Quiz

Keep learning

Related questions

View more