141

I have some data called stations which is an array containing objects.

stations : [
  {call:'station one',frequency:'000'},
  {call:'station two',frequency:'001'}
]

I'd like to render a ui component for each array position. So far I can write

 var stationsArr = []
 for (var i = 0; i < this.data.stations.length; i++) {
     stationsArr.push(
         <div className="station">
             {this.data}
         </div>
     )
 }

And then render

render(){
 return (
   {stationsArr}
 )
}

The problem is I'm getting all of the data printing out. I instead want to just show a key like {this.data.call} but that prints nothing.

How can I loop through this data and return a new UI element for each position of the array?

Elijah
  • 8,381
  • 2
  • 55
  • 49
thatgibbyguy
  • 4,033
  • 3
  • 23
  • 39

6 Answers6

194

You can map the list of stations to ReactElements.

With React >= 16, it is possible to return multiple elements from the same component without needing an extra html element wrapper. Since 16.2, there is a new syntax <> to create fragments. If this does not work or is not supported by your IDE, you can use <React.Fragment> instead. Between 16.0 and 16.2, you can use a very simple polyfill for fragments.

Try the following

// Modern syntax >= React 16.2.0
const Test = ({stations}) => (
  <>
    {stations.map(station => (
      <div key={station.call} className='station'>{station.call}</div>
    ))}
  </>
); 

// Modern syntax < React 16.2.0
// You need to wrap in an extra element like div here

const Test = ({stations}) => (
  <div>
    {stations.map(station => (
      <div className="station" key={station.call}>{station.call}</div>
    ))}
  </div>
); 

// old syntax
var Test = React.createClass({
    render: function() {
        var stationComponents = this.props.stations.map(function(station) {
            return <div className="station" key={station.call}>{station.call}</div>;
        });
        return <div>{stationComponents}</div>;
    }
});
 
var stations = [
  {call:'station one',frequency:'000'},
  {call:'station two',frequency:'001'}
]; 

ReactDOM.render(
  <div>
    <Test stations={stations} />
  </div>,
  document.getElementById('container')
);

Don't forget the key attribute!

https://jsfiddle.net/69z2wepo/14377/

im_infamous
  • 972
  • 1
  • 17
  • 29
Sebastien Lorber
  • 89,644
  • 67
  • 288
  • 419
  • @thatgibbyguy: Oh yes! that could be the right answer. There needs to be a wrapping around your child components. Your `render` function must return one single element. – Tahir Ahmed Aug 22 '15 at 15:11
  • What would be the reason for suggesting a key attribute for each station element? What I'm asking is, what would that change if it's not needed now? – thatgibbyguy Aug 26 '15 at 18:32
  • 5
    @thatgibbyguy in this case it does not bring much advantage. In more advanced examples it permits to have better rendering performances as React can easily knows if an existing node has been moved to another place in the station array, thus avoiding to destroy and recreate an existing dom node (and also keep the dom node mounted). It's in the react doc: https://facebook.github.io/react/docs/reconciliation.html#keys – Sebastien Lorber Aug 26 '15 at 19:12
  • Slightly off topic but I am not sure how to construct a query to ask this. When using the ES6 syntax in your example above, how would one go about passing in the index from the map? IOW, how can I figure out if I am in the last node of the array? I tried wrapping in parens and that didn't seem to go well. – Lane Goolsby Feb 24 '17 at 22:10
  • @ElHombre `stations.map((station,index) => { })` works fine for me – Sebastien Lorber Feb 24 '17 at 22:38
  • As of React@15.5.5, React.createClass will be moved to a seperate package, https://facebook.github.io/react/blog/2017/04/07/react-v15.5.0.html – julianljk Apr 20 '17 at 19:36
  • How to deal with the same situation when the component that renders an array, is inside a that does not allow
    ?@SebastienLorber
    – tommyalvarez Jan 19 '18 at 18:13
  • @tommyalvarez for that you need the new fragment API of react 16. There is no way otherwise. – Sebastien Lorber Jan 19 '18 at 18:46
  • The modern code didn't work form me. After 20 minutes I realized it was missing return statements. Worth noting you need to return each component from inside the map iteration. – ltrainpr Jun 24 '18 at 12:26
  • @ltrainpr if you use parenthesis instead of brackets you should not need to add a return statement, this is how ES6 works. – Sebastien Lorber Jun 24 '18 at 21:49
61

I have an answer that might be a bit less confusing for newbies like myself. You can just use map within the components render method.

render () {
   return (
       <div>
           {stations.map(station => <div key={station}> {station} </div>)} 
       </div>
   );
}
Ian Smith
  • 879
  • 1
  • 12
  • 23
Thomas Valadez
  • 1,697
  • 2
  • 22
  • 27
6

this.data presumably contains all the data, so you would need to do something like this:

var stations = [];
var stationData = this.data.stations;

for (var i = 0; i < stationData.length; i++) {
    stations.push(
        <div key={stationData[i].call} className="station">
            Call: {stationData[i].call}, Freq: {stationData[i].frequency}
        </div>
    )
}

render() {
  return (
    <div className="stations">{stations}</div>
  )
}

Or you can use map and arrow functions if you're using ES6:

const stations = this.data.stations.map(station =>
    <div key={station.call} className="station">
      Call: {station.call}, Freq: {station.frequency}
    </div>
);
Dominic
  • 62,658
  • 20
  • 139
  • 163
3

This is quite likely the simplest way to achieve what you are looking for.

In order to use this map function in this instance, we will have to pass a currentValue (always-required) parameter, as well an index (optional) parameter. In the below example, station is our currentValue, and x is our index.

station represents the current value of the object within the array as it is iterated over. x automatically increments; increasing by one each time a new object is mapped.

render () {
    return (
        <div>
            {stations.map((station, x) => (
                <div key={x}> {station} </div>
            ))}
        </div>
    );
}

What Thomas Valadez had answered, while it had provided the best/simplest method to render a component from an array of objects, it had failed to properly address the way in which you would assign a key during this process.

ZachHappel
  • 363
  • 2
  • 4
2

There are couple of way which can be used.

const stations = [
  {call:'station one',frequency:'000'},
  {call:'station two',frequency:'001'}
];
const callList = stations.map(({call}) => call)

Solution 1

<p>{callList.join(', ')}</p>

Solution 2

<ol>    
  { callList && callList.map(item => <li>{item}</li>) }
</ol>

Edit kind-antonelli-z8372

Of course there are other ways also available.

Mo.
  • 26,306
  • 36
  • 159
  • 225
1

Since React 16, it is completely valid to return an array of elements from a class component's render() method or a function component. See https://legacy.reactjs.org/blog/2017/09/26/react-v16.0.html#new-render-return-types-fragments-and-strings

There is no need for a wrapper element (like <div>) or a fragment. As such, the following code suffices for a function component:

const MyComponent = ({ stations }) => stations.map(station => 
           <div className="station" key={station.call}>{station.call}</div>);

However, it is important to keep in mind that every element in an array of elements that is rendered must have a unique key among the same array.

For example, React will warn about return [<div>Div 1</div>, <div>Div 2</div>] even though the array is static. It would have to be changed to add keys, e.g. return [<div key="div1">Div 1</div>, <div key="div2">Div 2</div>].

For a static number of values that need to be grouped together, fragments (introduced in React 16.2.0) are a better choice as they do not require keys on each element. This means that the following does not produce a warning:

return <>
    <div>Div 1</div>
    <div>Div 2</div>
</>;

In the case of mapping over an array, as in the question, keys are necessary regardless so fragments provide no advantage. The array after mapping can be directly returned instead.

Unmitigated
  • 76,500
  • 11
  • 62
  • 80