187

I'm trying to convert a jQuery component to React.js and one of the things I'm having difficulty with is rendering n number of elements based on a for loop.

I understand this is not possible, or recommended and that where an array exists in the model it makes complete sense to use map. That's fine, but what about when you do not have an array? Instead you have numeric value which equates to a given number of elements to render, then what should you do?

Here's my example, I want to prefix a element with an arbitrary number of span tags based on it's hierarchical level. So at level 3, I want 3 span tags before the text element.

In javascript:

for (var i = 0; i < level; i++) {
    $el.append('<span class="indent"></span>');
}
$el.append('Some text value');

I can't seem to get this, or anything similar to work in a JSX React.js component. Instead I had to do the following, first building a temp array to the correct length and then looping the array.

React.js

render: function() {
  var tmp = [];
  for (var i = 0; i < this.props.level; i++) {
    tmp.push(i);
  }
  var indents = tmp.map(function (i) {
    return (
      <span className='indent'></span>
    );
  });

  return (
    ...
    {indents}
    "Some text value"
    ...
  );
}

Surely this can't be the best, or only way to achieve this? What am I missing?

Dmitry Shvedov
  • 3,169
  • 4
  • 39
  • 51
Jon Miles
  • 9,605
  • 11
  • 46
  • 66

6 Answers6

279

Updated: As of React > 0.16

Render method does not necessarily have to return a single element. An array can also be returned.

var indents = [];
for (var i = 0; i < this.props.level; i++) {
  indents.push(<span className='indent' key={i}></span>);
}
return indents;

OR

return this.props.level.map((item, index) => (
    <span className="indent" key={index}>
        {index}
    </span>
));

Docs here explaining about JSX children


OLD:

You can use one loop instead

var indents = [];
for (var i = 0; i < this.props.level; i++) {
  indents.push(<span className='indent' key={i}></span>);
}
return (
   <div>
    {indents}
    "Some text value"
   </div>
);

You can also use .map and fancy es6

return (
   <div>
    {this.props.level.map((item, index) => (
       <span className='indent' key={index} />
    ))}
    "Some text value"
   </div>
);

Also, you have to wrap the return value in a container. I used div in the above example

As the docs say here

Currently, in a component's render, you can only return one node; if you have, say, a list of divs to return, you must wrap your components within a div, span or any other component.

Dhiraj
  • 33,140
  • 10
  • 61
  • 78
  • 1
    That works, and it's much simpler thanks. Yes I'm aware that you have to wrap return value in container, I'm doing that already it's just missing from the example. – Jon Miles Mar 19 '15 at 16:06
  • 2
    add keys inside the loop. keys are important in react. – Aamir Afridi Jan 28 '16 at 21:11
  • in the loop, need to use `key={i}`, *not* `key=i`, because the latter throws `JSX value should be either an expression or a quoted JSX text`. – hexicle Mar 31 '16 at 01:06
  • 4
    i've been looking for you all of my life – olleh Aug 04 '16 at 16:40
  • so it is important to wrap the element in return ? Is there any alternative without wrapping up we can return multiple elements – Kushal Jain Sep 14 '16 at 06:36
  • What if I want to only render the {indents}, without the enclosing div? –  Oct 03 '16 at 04:51
  • 2
    @ElgsQianChen Its not possible. It has to be wrapped with some tag. If {indents} returns a single dom elements with content inside it, then its ok – Dhiraj Oct 03 '16 at 04:58
  • If you are using any linting, you may get a warning to not use your index as the key. The warning comes appears for either iterative approach. You could abstract the key's reference outside of your function: ```let myKey = 0; array.map((item, index) => { myKey += 1; return ( ); });``` . This is pretty simplistic. For a more complex app, you may want to ensure the key is unique. You could prepend or append an additional identifier to make it so. – Victor Feb 08 '19 at 22:01
  • 6
    I don't understand why the map method is mentioned in this answer, as it does only work for Array objects, which the question clearly states is not the case. – flukyspore Aug 31 '19 at 14:56
50

Here is more functional example with some ES6 features:

'use strict';

const React = require('react');

function renderArticles(articles) {
    if (articles.length > 0) {      
        return articles.map((article, index) => (
            <Article key={index} article={article} />
        ));
    }
    else return [];
}

const Article = ({article}) => {
    return ( 
        <article key={article.id}>
            <a href={article.link}>{article.title}</a>
            <p>{article.description}</p>
        </article>
    );
};

const Articles = React.createClass({
    render() {
        const articles = renderArticles(this.props.articles);

        return (
            <section>
                { articles }
            </section>
        );
    }
});

module.exports = Articles;
Dmytro Medvid
  • 4,988
  • 3
  • 25
  • 29
  • 1
    This looks like the most 'Reacty' way to do it. Pass values as props to another sub component. Thanks! – Michael Giovanni Pumo Nov 16 '16 at 18:00
  • This is a great! Perfect for when your render() is html heavy. – Matt Jan 31 '17 at 23:16
  • To make it more ES6 you could use `import React from "react"` and `export default Articles` – jonlink Mar 15 '17 at 12:47
  • 3
    This answer doesn't even attempt to answer the question. The question was clear, how to convert a `for loop` to a map'able array (or object) in order to render n number of items in a React component without having an array of items. You solution completely ignores that fact and assumes being passed an array of articles from props. – Jon Miles Feb 11 '18 at 13:36
  • The question is very specific about NOT having an array of objects. – pbuzz007 Jan 02 '22 at 03:42
34

Array.from() takes an iterable object to convert to an array and an optional map function. You could create an object with a .length property as follows:

return Array.from({length: this.props.level}, (item, index) => 
  <span className="indent" key={index}></span>
);
conradj
  • 2,481
  • 2
  • 24
  • 30
16

I'm using Object.keys(chars).map(...) to loop in render

// chars = {a:true, b:false, ..., z:false}

render() {
    return (
       <div>
        {chars && Object.keys(chars).map(function(char, idx) {
            return <span key={idx}>{char}</span>;
        }.bind(this))}
        "Some text value"
       </div>
    );
}
Mathdoy
  • 505
  • 5
  • 6
  • Your answer worked for me, but only after I added `chars && ...` and `.bind(this)` on the end of my function. I'm curious why merely `Object...`(so on and so forth) didn't work. I kept getting undefined. – m00saca Apr 30 '17 at 21:15
  • 2
    This doesn't answer the question, it specifically says without an array of objects to parse and the explanation explicitly says that I want to convert a for loop to map for rendering in a React component. You substituted array for an object which doesn't help answer the question, or add any further value. – Jon Miles Feb 11 '18 at 13:30
6

You can still use map if you can afford to create a makeshift array:

{
    new Array(this.props.level).fill(0).map((_, index) => (
        <span className='indent' key={index}></span>
    ))
}

This works because new Array(n).fill(x) creates an array of size n filled with x, which can then aid map.

array-fill

SNag
  • 17,681
  • 10
  • 54
  • 69
-1

I think this is the easiest way to loop in react js

<ul>
    {yourarray.map((item)=><li>{item}</li>)}
</ul>
Jon Miles
  • 9,605
  • 11
  • 46
  • 66