Loop inside React JSX Solutions

Problem:

Loop inside React JSX

I’m trying to do something like the following in React JSX (where ObjectRow is a separate component):

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

I realize and understand why this isn’t valid JSX, since JSX maps to function calls. However, coming from template land and being new to JSX, I am unsure how I would achieve the above (adding a component multiple times).

Solutions: Loop inside React JSX Solutions

Solution 1:

Think of it like you’re just calling JavaScript functions. You can’t use a for loop where the arguments to a function call would go:

return tbody(
    for (var i = 0; i < numrows; i++) {
        ObjectRow()
    } 
)

See how the function tbody is being passed a for loop as an argument – leading to a syntax error.

But you can make an array, and then pass that in as an argument:

var rows = [];
for (var i = 0; i < numrows; i++) {
    rows.push(ObjectRow());
}
return tbody(rows);

You can basically use the same structure when working with JSX:

var rows = [];
for (var i = 0; i < numrows; i++) {
    // note: we are adding a key prop here to allow react to uniquely identify each
    // element in this array. see: https://reactjs.org/docs/lists-and-keys.html
    rows.push(<ObjectRow key={i} />);
}
return <tbody>{rows}</tbody>;

Incidentally, my JavaScript example is almost exactly what that example of JSX transforms into. Play around with Babel REPL to get a feel for how JSX works.

Solution 2:

I am not sure if this will work for your situation, but often map is a good answer.

If this was your code with the for loop:

<tbody>
    for (var i=0; i < objects.length; i++) {
        <ObjectRow obj={objects[i]} key={i}>
    }
</tbody>

You could write it like this with map:

<tbody>
    {objects.map(function(object, i){
        return <ObjectRow obj={object} key={i} />;
    })}
</tbody>

ES6 syntax:

<tbody>
    {objects.map((object, i) => <ObjectRow obj={object} key={i} />)}
</tbody>

Solution 3:

If you don’t already have an array to map() like @FakeRainBrigand’s answer, and want to inline this so the source layout corresponds to the output closer than @SophieAlpert’s answer:

With ES2015 (ES6) syntax (spread and arrow functions)
<tbody>
  {[...Array(10)].map((x, i) =>
    <ObjectRow key={i} />
  )}
</tbody>

Re: transpiling with Babel, its caveats page says that Array.from is required for spread, but at present (v5.8.23) that does not seem to be the case when spreading an actual Array. I have a documentation issue open to clarify that. But use at your own risk or polyfill.

Vanilla ES5

Array.apply

<tbody>
  {Array.apply(0, Array(10)).map(function (x, i) {
    return <ObjectRow key={i} />;
  })}
</tbody>

Inline IIFE

<tbody>
  {(function (rows, i, len) {
    while (++i <= len) {
      rows.push(<ObjectRow key={i} />)
    }
    return rows;
  })([], 0, 10)}
</tbody>

Combination of techniques from other answers

Keep the source layout corresponding to the output, but make the inlined part more compact:

render: function () {
  var rows = [], i = 0, len = 10;
  while (++i <= len) rows.push(i);

  return (
    <tbody>
      {rows.map(function (i) {
        return <ObjectRow key={i} index={i} />;
      })}
    </tbody>
  );
}

With ES2015 syntax & Array methods

With Array.prototype.fill you could do this as an alternative to using spread as illustrated above:

<tbody>
  {Array(10).fill(1).map((el, i) =>
    <ObjectRow key={i} />
  )}
</tbody>

(I think you could actually omit any argument to fill(), but I’m not 100% on that.) Thanks to @FakeRainBrigand for correcting my mistake in an earlier version of the fill() solution (see revisions).

key

In all cases the key attr alleviates a warning with the development build but isn’t accessible in the child. You can pass an extra attr if you want the index available to the child. See Lists and Keys for discussion.

Solution 4:

Simply using the map Array method with ES6 syntax:

<tbody>
  {items.map(item => <ObjectRow key={item.id} name={item.name} />)} 
</tbody>

Don’t forget the key property.

Using the Array map function is a very common way to loop through an Array of elements and create components according to them in React. This is a great way to do a loop which is a pretty efficient and is a tidy way to do your loops in JSX. It’s not the only way to do it, but the preferred way.

Also, don’t forget having a unique Key for each iteration as required. The map function creates a unique index from 0, but it’s not recommended using the produced index, but if your value is unique or if there is a unique key, you can use them:

<tbody>
  {numrows.map(x=> <ObjectRow key={x.id} />)}
</tbody>

Also, a few lines from MDN if you not familiar with the map function on Array:

map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values, including undefined. It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).

callback is invoked with three arguments: the value of the element, the index of the element, and the Array object being traversed.

If a thisArg parameter is provided to the map, it will be used as callback’s this value. Otherwise, the value undefined will be used as its this value. This value ultimately observable by the callback is determined according to the usual rules for determining the this seen by a function.

map does not mutate the array on which it is called (although callback, if invoked, may do so).

Solution 5:

If you’re already using lodash, the _.times function is handy.

import React, { Component } from "react";
import Select from "./Select";
import _ from "lodash";

export default class App extends Component {
  render() {
    return (
      <div className="container">
        <ol>
          {_.times(3, (i) => (
            <li key={i}>repeated 3 times</li>
          ))}
        </ol>
      </div>
    );
  }
}

Only import times from Lodash, not the entire library. It’s okay during development to avoid backwards incompatibility as Lodash releases updates, but in production you’re costing seconds importing an entire library.

Solution 6:

There are multiple ways to go about doing this. JSX eventually gets compiled to JavaScript, so as long as you’re writing valid JavaScript, you’ll be good.

My answer aims to consolidate all the wonderful ways already presented here:

If you do not have an array of object, simply the number of rows:

Within the return block, creating an Array and using Array.prototype.map:

render() {
  return (
    <tbody>
      {Array(numrows).fill(null).map((value, index) => (
        <ObjectRow key={index}>
      ))}
    </tbody>
  );
}

Outside the return block, simply use a normal JavaScript for loop:

render() {
  let rows = [];
  for (let i = 0; i < numrows; i++) {
    rows.push(<ObjectRow key={i}/>);
  }
  return (
    <tbody>{rows}</tbody>
  );
}

Immediately invoked function expression:

render() {
  return (
    <tbody>
      {() => {
        let rows = [];
        for (let i = 0; i < numrows; i++) {
          rows.push(<ObjectRow key={i}/>);
        }
        return rows;
      }}
    </tbody>
  );
}

If you have an array of objects

Within the return block, .map() each object to a <ObjectRow> component:

render() {
  return (
    <tbody>
      {objectRows.map((row, index) => (
        <ObjectRow key={index} data={row} />
      ))}
    </tbody>
  );
}

Outside the return block, simply use a normal JavaScript for loop:

render() {
  let rows = [];
  for (let i = 0; i < objectRows.length; i++) {
    rows.push(<ObjectRow key={i} data={objectRows[i]} />);
  }
  return (
    <tbody>{rows}</tbody>
  );
}

Immediately invoked function expression:

render() {
  return (
    <tbody>
      {(() => {
        const rows = [];
        for (let i = 0; i < objectRows.length; i++) {
          rows.push(<ObjectRow key={i} data={objectRows[i]} />);
        }
        return rows;
      })()}
    </tbody>
  );
}

Solution 7:

You can also extract outside the return block:

render: function() {
    var rows = [];
    for (var i = 0; i < numrows; i++) {
        rows.push(<ObjectRow key={i}/>);
    } 

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

Solution 8:

If you opt to convert this inside return() of the render method, the easiest option would be using the map( ) method. Map your array into JSX syntax using the map() function, as shown below (ES6 syntax is used).


Inside the parent component:

<tbody>
   { objectArray.map(object => <ObjectRow key={object.id} object={object.value} />) }
</tbody>

Please note the key attribute is added to your child component. If you didn’t provide a key attribute, you can see the following warning on your console.

Warning: Each child in an array or iterator should have a unique “key” prop.

Note: One common mistake people do is using index as the key when iterating. Using index of the element as a key is an antipattern, and you can read more about it here. In short, if it’s not a static list, never use index as the key.


Now at the ObjectRow component, you can access the object from its properties.

Inside the ObjectRow component

const { object } = this.props

Or

const object = this.props.object

This should fetch you the object you passed from the parent component to the variable object in the ObjectRow component. Now you can spit out the values in that object according to your purpose.


References:

map() method in JavaScript

ECMAScript 6 or ES6

Conclusion:

I hope this Loop inside React JSX Solutions would be useful for you to learn something new from this solution. If it helped you then don’t forget to bookmark our site for more Quiz Answers and solutions.

Check Solutions on Stack overflow

This solution is intended for audiences of all experiences who are interested in learning about new skills in a business context; there are no prerequisite courses.

Keep Learning!

Leave a Reply

Your email address will not be published.

error: Content is protected !!