¿Cómo hacer un bucle y renderizar elementos en React.js sin un array de objetos para mapear?

Estoy tratando de convertir un componente jQuery a React.js y una de las cosas con las que estoy teniendo dificultades es con el renderizado de n número de elementos basado en un bucle for.

Entiendo que esto no es posible, o recomendado y que cuando existe un array en el modelo tiene todo el sentido utilizar map. Eso está bien, pero ¿qué pasa cuando no tienes un array? En lugar de eso, tienes un valor numérico que equivale a un número determinado de elementos a renderizar, entonces ¿qué debes hacer?

Este es mi ejemplo, quiero anteponer a un elemento un número arbitrario de etiquetas span basado en su nivel jerárquico. Así que en el nivel 3, quiero 3 etiquetas span antes del elemento de texto.

En javascript:

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

Parece que no puedo conseguir esto, o algo similar para trabajar en un componente JSX React.js. En su lugar, tuve que hacer lo siguiente, primero la construcción de una matriz temporal a la longitud correcta y luego el bucle de la matriz.

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"
    ...
  );
}

Seguramente esta no puede ser la mejor, o la única manera de lograr esto? ¿Qué me estoy perdiendo?

Solución

Actualizado: A partir de React > 0.16

El método Render no tiene que devolver necesariamente un solo elemento. También se puede devolver un array.

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

O

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

Docs aquí explicando sobre los niños JSX


OLD:

Puedes utilizar un bucle en su lugar

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>
);

También puede utilizar .map y es6 de lujo

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

Además, tienes que envolver el valor de retorno en un contenedor. He utilizado div en el ejemplo anterior

Como dice la documentación aquí

Actualmente, en el render de un componente, sólo puedes devolver un nodo; si tienes, por ejemplo, una lista de divs para devolver, debes envolver tus componentes dentro de un div, span o cualquier otro componente.

Comentarios (9)

Aquí hay un ejemplo más funcional con algunas características de ES6:

'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;
Comentarios (5)

Estoy usando Object.keys(chars).map(...) para hacer un bucle en el 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>
    );
}
Comentarios (2)