How to dynamically generate rows in a table? [duplicate] - reactjs
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).
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 (let 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:
const rows = [];
for (let i = 0; i < numrows; i++) {
rows.push(ObjectRow());
}
return tbody(rows);
You can basically use the same structure when working with JSX:
const rows = [];
for (let 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.
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>
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)
http://plnkr.co/edit/mfqFWODVy8dKQQOkIEGV?p=preview
<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
http://plnkr.co/edit/4kQjdTzd4w69g8Suu2hT?p=preview
<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 in the child. See Lists and Keys for discussion.
Simply using 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).
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>
);
}
}
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>
);
}
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>);
}
You might want to checkout React Templates, which does let you use JSX-style templates in React, with a few directives (such as rt-repeat).
Your example, if you used react-templates, would be:
<tbody>
<ObjectRow rt-repeat="obj in objects"/>
</tbody>
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
If numrows is an array, it's very simple:
<tbody>
{numrows.map(item => <ObjectRow />)}
</tbody>
The array data type in React is much better. An array can back a new array, and support filter, reduce, etc.
There are several answers pointing to using the map statement. Here is a complete example using an iterator within the FeatureList component to list Feature components based on a JSON data structure called features.
const FeatureList = ({ features, onClickFeature, onClickLikes }) => (
<div className="feature-list">
{features.map(feature =>
<Feature
key={feature.id}
{...feature}
onClickFeature={() => onClickFeature(feature.id)}
onClickLikes={() => onClickLikes(feature.id)}
/>
)}
</div>
);
You can view the complete FeatureList code on GitHub. The features fixture is listed here.
Let us say we have an array of items in your state:
[{name: "item1", id: 1}, {name: "item2", id: 2}, {name: "item3", id: 3}]
<tbody>
{this.state.items.map((item) => {
<ObjectRow key={item.id} name={item.name} />
})}
</tbody>
To loop for a number of times and return, you can achieve it with the help of from and map:
<tbody>
{
Array.from(Array(i)).map(() => <ObjectRow />)
}
</tbody>
where i = number of times
If you want to assign unique key IDs into the rendered components, you can use React.Children.toArray as proposed in the React documentation
React.Children.toArray
Returns the children opaque data structure as a flat array with keys assigned to each child. Useful if you want to manipulate collections of children in your render methods, especially if you want to reorder or slice this.props.children before passing it down.
Note:
React.Children.toArray() changes keys to preserve the semantics of nested arrays when flattening lists of children. That is, toArray prefixes each key in the returned array so that each element’s key is scoped to the input array containing it.
<tbody>
{
React.Children.toArray(
Array.from(Array(i)).map(() => <ObjectRow />)
)
}
</tbody>
An ECMAScript 2015 / Babel possibility is using a generator function to create an array of JSX:
function* jsxLoop(times, callback)
{
for(var i = 0; i < times; ++i)
yield callback(i);
}
...
<tbody>
{[...jsxLoop(numrows, i =>
<ObjectRow key={i}/>
)]}
</tbody>
This can be done in multiple ways.
As suggested above, before return store all elements in the array
Loop inside return
Method 1:
let container = [];
let arr = [1, 2, 3] //can be anything array, object
arr.forEach((val, index) => {
container.push(
<div key={index}>
val
</div>)
/**
* 1. All loop generated elements require a key
* 2. only one parent element can be placed in Array
* e.g. container.push(
* <div key={index}>
val
</div>
<div>
this will throw error
</div>
)
**/
});
return (
<div>
<div>any things goes here</div>
<div>{container}</div>
</div>
)
Method 2:
return (
<div>
<div>any things goes here</div>
<div>
{
(() => {
let container = [];
let arr = [1, 2, 3] //can be anything array, object
arr.forEach((val, index) => {
container.push(
<div key={index}>
val
</div>)
});
return container;
})()
}
</div>
</div>
)
ES2015 Array.from with the map function + key
If you have nothing to .map() you can use Array.from() with the map function to repeat elements:
<tbody>
{Array.from({ length: 5 }, (value, key) => <ObjectRow key={key} />)}
</tbody>
...Or you can also prepare an array of objects and map it to a function to have the desired output. I prefer this, because it helps me to maintain the good practice of coding with no logic inside the return of render.
render() {
const mapItem = [];
for(let i =0;i<item.length;i++)
mapItem.push(i);
const singleItem => (item, index) {
// item the single item in the array
// the index of the item in the array
// can implement any logic here
return (
<ObjectRow/>
)
}
return(
<tbody>{mapItem.map(singleItem)}</tbody>
)
}
I use this:
gridItems = this.state.applications.map(app =>
<ApplicationItem key={app.Id} app={app } />
);
PS: never forget the key or you will have a lot of warnings!
Simply use .map() to loop through your collection and return <ObjectRow> items with props from each iteration.
Assuming objects is an array somewhere...
<tbody>
{ objects.map((obj, index) => <ObjectRow obj={ obj } key={ index }/> ) }
</tbody>
You can of course solve with a .map as suggested by the other answer. If you already use Babel, you could think about using jsx-control-statements.
They require a little of setting, but I think it's worth in terms of readability (especially for non-React developer).
If you use a linter, there's also eslint-plugin-jsx-control-statements.
Here's a simple solution to it.
var Object_rows = [];
for (var i = 0; i < numrows; i++) {
Object_rows.push(<ObjectRow />);
}
<tbody>{Object_rows}</tbody>;
No mapping and complex code is required. You just need to push the rows to the array and return the values to render it.
Your JSX code will compile into pure JavaScript code, any tags will be replaced by ReactElement objects. In JavaScript, you cannot call a function multiple times to collect their returned variables.
It is illegal, the only way is to use an array to store the function returned variables.
Or you can use Array.prototype.map which is available since JavaScript ES5 to handle this situation.
Maybe we can write other compiler to recreate a new JSX syntax to implement a repeat function just like Angular's ng-repeat.
I tend to favor an approach where programming logic happens outside the return value of render. This helps keep what is actually rendered easy to grok.
So I'd probably do something like:
import _ from 'lodash';
...
const TableBody = ({ objects }) => {
const objectRows = objects.map(obj => <ObjectRow object={obj} />);
return <tbody>{objectRows}</tbody>;
}
Admittedly this is such a small amount of code that inlining it might work fine.
Here is a sample from the React documentation, JavaScript Expressions as Children:
function Item(props) {
return <li>{props.message}</li>;
}
function TodoList() {
const todos = ['finish doc', 'submit pr', 'nag dan to review'];
return (
<ul>
{todos.map((message) => <Item key={message} message={message} />)}
</ul>
);
}
As for your case, I suggest writing like this:
function render() {
return (
<tbody>
{numrows.map((roe, index) => <ObjectRow key={index} />)}
</tbody>
);
}
Please notice the key is very important, because React use the key to differ data in array.
You may use .map() in a React for loop.
<tbody>
{ newArray.map(() => <ObjectRow />) }
</tbody>
Since you are writing JavaScript syntax inside JSX code, you need to wrap your JavaScript code in curly braces.
row = () => {
var rows = [];
for (let i = 0; i<numrows; i++) {
rows.push(<ObjectRow/>);
}
return rows;
}
<tbody>
{this.row()}
</tbody>
You can also use a self-invoking function:
return <tbody>
{(() => {
let row = []
for (var i = 0; i < numrows; i++) {
row.push(<ObjectRow key={i} />)
}
return row
})()}
</tbody>
I use it like
<tbody>
{ numrows ? (
numrows.map(obj => { return <ObjectRow /> })
) : null
}
</tbody>
If you really want a for loop equivalent (you have a single number, not an array), just use range from Lodash.
Don't reinvent the wheel and don't obfuscate your code. Just use the standard utility library.
import range from 'lodash/range'
range(4);
// => [0, 1, 2, 3]
range(1, 5);
// => [1, 2, 3, 4]
Related
React - how to render multiple buttons with a regular for loop?
Sorry if its very basic but: when rendering multiple buttons (0-9) in an iteration - What is the difference btw map and for loop ? Why does the for loop only renders the first element (0) while map works fine? Why do I have to first push the buttons into an array and return that then (as seen on other examples) ? Can I use regular for loop and render buttons without pushing it into an arary? Thanks! import React from 'react'; const Keys = () => { const renderKeys = () => { //works fine var arr = [1,2,3,4,5,6,7,8,9] return arr.map((val) => { return <button>{val}</button> }) }; const renderKeys = () => { //does not work for (var i=0; i<10; i++) { return <button>{i}</button> } }; return ( <div> {renderKeys()} </div> ) };
When you call return inside a for-loop it stops executing the loop. That's why you only get back the first button. However, calling return inside a .map() will not stop the loop from iterating. Instead you use return to explicitly define what you want to have in a new array. Note that .map() creates a brand new array by using elements from an existing array. You are free to utilize those elements any way you want which makes it suitable for rendering JSX. Example: const numbers = [1, 2, 3, 4, 5] const numbersMultipledByTwo = numbers.map((number) => { return <div>{ number * 2 }</div> }) Theoretically, you could accomplish the same effect using a for-loop but that will also require help from a second array. Working code: import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; class App extends React.Component{ getButtonsUsingMap = () => { const array = [1, 2, 3 ,4, 5] return array.map((number) => { return <button>{number}</button> }) } getButtonsUsingForLoop = (num) => { const array = [] for(var i = 1; i <= num; i++){ array.push(<button>{i}</button>) } return array } render(){ return( <div> <h4>Using .map()</h4> {this.getButtonsUsingMap()} <h4>using for-loop</h4> {this.getButtonsUsingForLoop(5)} </div> ) } } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement); In that getButtonsUsingForLoop function, you can see there are more explicit demands to make it work. First we need to satisfy the argument. Then initialize a new array. Then define a boundary for the loop. Iterate and push JSX to the empty-array. Then finally return that array. So the logic is not very succinct. Whereas on the other-hand, a .map() essentially handles all of that. As long as you have a pre-existing array to iterate over (which 99% of the time you will be dealing with some sort of state or props-array.) See sandbox: https://codesandbox.io/s/pensive-leftpad-lt5ml
What is the difference btw map and for loop? .map() iterates an array (and arrays only) while a for loop could be lazily summarized as a more "general" loop mechanism that is independent of any specific data type. Why does the for loop only renders the first element (0) while map works fine? Because you're returning from the function in the first iteration of the for loop with const renderKeys = () => { //does not work for (var i=0; i<10; i++) { return <button>{i}</button> // this returns from the function you're in } }; .map() works fine because it returns a new array from iterating the input-array, e.g.: const renderKeys = () => { // works fine var arr = [1,2,3,4,5,6,7,8,9] return arr.map((val) => { // here you return the new array created by map return <button>{val}</button> }); }; Why do I have to first push the buttons into an array and return that then (as seen on other examples)? basically to "imitate" what map does, e.g. creating a new array from the iteration. Can I use regular for loop and render buttons without pushing it into an array? Directly return you mean? I don't think it's possible, but maybe somebody else does know a way! Why I think it's not possible? return ( <div> {renderKeys()} </div> ) in JSX you return a function and you can't pass a for loop as a function argument directly, e.g. this: return ( <div> for (var i=0; i<10; i++) { <button>{i}</button> } </div> ) would most likely give you a syntax error...
Set the number of cells in the table of components in JSX
I have simple component that represents instances of other component as html table. class Table extends Component { render() { const { data } = this.props; const articles= data.map((el, i) => <tr><td><Post post={el}/></td></tr>); return <table> {articles} </table>; } Now I have only one table cell per row. I want to place three cells per row, for example. I tried something like this const articles = data.map(function(el, i) { i++; if (i == 1) return <tr><td><Post post={el}/></td>; else if (i % 3 == 0) return <td><Post post={el}/></td></tr>; else if (i % 4 == 0) return <tr><td><Post post={el}/></td>; else return <td><Post post={el}/></td>; }); This is a bad way, anyway. But in JSX this is impossible, because I get error "unterminated JSX contents". ReactTable is not suitable for me, because I have table of another components, not data grid.
You can use lodash chunk to create an array with arrays where each sub-array represent a row. Example class Table extends React.Component { render() { const { data } = this.props; const rows = _.chunk(data, 3); const articles = rows.map((row, i) => ( <tr key={i}> {row.map((cell, i) => ( <td key={i}>{cell}</td> ))} </tr> )); return <table>{articles}</table>; } } ReactDOM.render( <Table data={[1, 2, 3, 4, 5, 6, 7]} />, document.getElementById("root") ); <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <div id="root"></div>
An array of data should be split into chunks with any suitable implementation like Lodash. Then arrays of arrays can be processed: return <table> {_.chunk(data, 3).map(row => ( <tr>{row.map(el => ( <td><Post post={el}/></td> )}</tr> )} </table>;
You are going about this the wrong way. A much simpler solution would be to simply manipulate your data into the shape in which you want to work with, and then write your code for rendering it. That is..convert your data into a 2 dimensional array with 3 columns and ceil(n/3) columns where is the number of elements in your array. Then you can simply map over the first dimension to create your <tr><tr/> elements and for each row you can map over the 3 elements to create your <td></td> elements. Note that I am not saying that you cannot do this without creating this intermediate data source, merely that it is always easier if your data structure resembles your component hierarchy.
how do I read the elements in an array one at a time in reactjs [duplicate]
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).
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 (let 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: const rows = []; for (let i = 0; i < numrows; i++) { rows.push(ObjectRow()); } return tbody(rows); You can basically use the same structure when working with JSX: const rows = []; for (let 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.
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>
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) http://plnkr.co/edit/mfqFWODVy8dKQQOkIEGV?p=preview <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 http://plnkr.co/edit/4kQjdTzd4w69g8Suu2hT?p=preview <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 in the child. See Lists and Keys for discussion.
Simply using 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).
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> ); } }
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> ); }
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>); }
You might want to checkout React Templates, which does let you use JSX-style templates in React, with a few directives (such as rt-repeat). Your example, if you used react-templates, would be: <tbody> <ObjectRow rt-repeat="obj in objects"/> </tbody>
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
If numrows is an array, it's very simple: <tbody> {numrows.map(item => <ObjectRow />)} </tbody> The array data type in React is much better. An array can back a new array, and support filter, reduce, etc.
There are several answers pointing to using the map statement. Here is a complete example using an iterator within the FeatureList component to list Feature components based on a JSON data structure called features. const FeatureList = ({ features, onClickFeature, onClickLikes }) => ( <div className="feature-list"> {features.map(feature => <Feature key={feature.id} {...feature} onClickFeature={() => onClickFeature(feature.id)} onClickLikes={() => onClickLikes(feature.id)} /> )} </div> ); You can view the complete FeatureList code on GitHub. The features fixture is listed here.
Let us say we have an array of items in your state: [{name: "item1", id: 1}, {name: "item2", id: 2}, {name: "item3", id: 3}] <tbody> {this.state.items.map((item) => { <ObjectRow key={item.id} name={item.name} /> })} </tbody>
To loop for a number of times and return, you can achieve it with the help of from and map: <tbody> { Array.from(Array(i)).map(() => <ObjectRow />) } </tbody> where i = number of times If you want to assign unique key IDs into the rendered components, you can use React.Children.toArray as proposed in the React documentation React.Children.toArray Returns the children opaque data structure as a flat array with keys assigned to each child. Useful if you want to manipulate collections of children in your render methods, especially if you want to reorder or slice this.props.children before passing it down. Note: React.Children.toArray() changes keys to preserve the semantics of nested arrays when flattening lists of children. That is, toArray prefixes each key in the returned array so that each element’s key is scoped to the input array containing it. <tbody> { React.Children.toArray( Array.from(Array(i)).map(() => <ObjectRow />) ) } </tbody>
An ECMAScript 2015 / Babel possibility is using a generator function to create an array of JSX: function* jsxLoop(times, callback) { for(var i = 0; i < times; ++i) yield callback(i); } ... <tbody> {[...jsxLoop(numrows, i => <ObjectRow key={i}/> )]} </tbody>
This can be done in multiple ways. As suggested above, before return store all elements in the array Loop inside return Method 1: let container = []; let arr = [1, 2, 3] //can be anything array, object arr.forEach((val, index) => { container.push( <div key={index}> val </div>) /** * 1. All loop generated elements require a key * 2. only one parent element can be placed in Array * e.g. container.push( * <div key={index}> val </div> <div> this will throw error </div> ) **/ }); return ( <div> <div>any things goes here</div> <div>{container}</div> </div> ) Method 2: return ( <div> <div>any things goes here</div> <div> { (() => { let container = []; let arr = [1, 2, 3] //can be anything array, object arr.forEach((val, index) => { container.push( <div key={index}> val </div>) }); return container; })() } </div> </div> )
ES2015 Array.from with the map function + key If you have nothing to .map() you can use Array.from() with the map function to repeat elements: <tbody> {Array.from({ length: 5 }, (value, key) => <ObjectRow key={key} />)} </tbody>
...Or you can also prepare an array of objects and map it to a function to have the desired output. I prefer this, because it helps me to maintain the good practice of coding with no logic inside the return of render. render() { const mapItem = []; for(let i =0;i<item.length;i++) mapItem.push(i); const singleItem => (item, index) { // item the single item in the array // the index of the item in the array // can implement any logic here return ( <ObjectRow/> ) } return( <tbody>{mapItem.map(singleItem)}</tbody> ) }
I use this: gridItems = this.state.applications.map(app => <ApplicationItem key={app.Id} app={app } /> ); PS: never forget the key or you will have a lot of warnings!
Simply use .map() to loop through your collection and return <ObjectRow> items with props from each iteration. Assuming objects is an array somewhere... <tbody> { objects.map((obj, index) => <ObjectRow obj={ obj } key={ index }/> ) } </tbody>
You can of course solve with a .map as suggested by the other answer. If you already use Babel, you could think about using jsx-control-statements. They require a little of setting, but I think it's worth in terms of readability (especially for non-React developer). If you use a linter, there's also eslint-plugin-jsx-control-statements.
Here's a simple solution to it. var Object_rows = []; for (var i = 0; i < numrows; i++) { Object_rows.push(<ObjectRow />); } <tbody>{Object_rows}</tbody>; No mapping and complex code is required. You just need to push the rows to the array and return the values to render it.
Your JSX code will compile into pure JavaScript code, any tags will be replaced by ReactElement objects. In JavaScript, you cannot call a function multiple times to collect their returned variables. It is illegal, the only way is to use an array to store the function returned variables. Or you can use Array.prototype.map which is available since JavaScript ES5 to handle this situation. Maybe we can write other compiler to recreate a new JSX syntax to implement a repeat function just like Angular's ng-repeat.
I tend to favor an approach where programming logic happens outside the return value of render. This helps keep what is actually rendered easy to grok. So I'd probably do something like: import _ from 'lodash'; ... const TableBody = ({ objects }) => { const objectRows = objects.map(obj => <ObjectRow object={obj} />); return <tbody>{objectRows}</tbody>; } Admittedly this is such a small amount of code that inlining it might work fine.
Here is a sample from the React documentation, JavaScript Expressions as Children: function Item(props) { return <li>{props.message}</li>; } function TodoList() { const todos = ['finish doc', 'submit pr', 'nag dan to review']; return ( <ul> {todos.map((message) => <Item key={message} message={message} />)} </ul> ); } As for your case, I suggest writing like this: function render() { return ( <tbody> {numrows.map((roe, index) => <ObjectRow key={index} />)} </tbody> ); } Please notice the key is very important, because React use the key to differ data in array.
You may use .map() in a React for loop. <tbody> { newArray.map(() => <ObjectRow />) } </tbody>
Since you are writing JavaScript syntax inside JSX code, you need to wrap your JavaScript code in curly braces. row = () => { var rows = []; for (let i = 0; i<numrows; i++) { rows.push(<ObjectRow/>); } return rows; } <tbody> {this.row()} </tbody>
You can also use a self-invoking function: return <tbody> {(() => { let row = [] for (var i = 0; i < numrows; i++) { row.push(<ObjectRow key={i} />) } return row })()} </tbody>
I use it like <tbody> { numrows ? ( numrows.map(obj => { return <ObjectRow /> }) ) : null } </tbody>
If you really want a for loop equivalent (you have a single number, not an array), just use range from Lodash. Don't reinvent the wheel and don't obfuscate your code. Just use the standard utility library. import range from 'lodash/range' range(4); // => [0, 1, 2, 3] range(1, 5); // => [1, 2, 3, 4]
Reactjs, how to instanciate object from array, and update render
I'm struggling with reactjs for no reason. I'm a little confused about the magic behind and I'm not able to perform a simple operation of adding object / removing object from an array and display it. I my parent, I have a method which on click append a new element: appendNewPma(){ var newPma = this.state.pma.slice(); newPma.push(PmaType1); this.setState({pma:newPma}) } then my render method is like that: render() { return ( <div> <a className="waves-effect waves-light btn" onClick={this.appendNewPma}>new</a> {this.state.pma.map((Item, index) => ( <Item key = {index} ref = {"pma" + index.toString()} onDelete = {() => this.onDelete(index)} title = {index}/> ))} </div> ); } Append work fine, but my array doesn't contain an object to display but rather a magical function that I don't understand. But when I try to delete an object: onDelete(idx){ console.log(idx); var pma = this.state.pma.slice(); pma.splice(idx, 1); this.setState({pma:pma}) } When I delete from the array, no matter what index I will remove, it will only remove the last object. I know my code is not ok, but I have no idea how you can render element for an array of object (here my array is list of function constructor). It will work better if I could get a straight ref to my object. Of course, I tryed to removed from the ReactDom, but was complening I was not updating from the parent... I just want a simple array push/pop pattern with update. Thanks for your help
Try below code. hope so it solve your issue. addToArray = (event) => { this.state.pma.push({"name ": "xyz"}); this.setState( this.state ) } removeFromArray =(index) => { var updatedArr = this.state.pma.splice(index, 1); this.setState({arr : updatedArr}) }
React/JSX - How to render a list alphabetically
I am working on someone else's code and trying to figure out how to render a list alphabetically in React. I didn't write any of this and have very little knowledge of React, so please bear with me. The ul looks like this: <ul className="prod-group__filter__options" ref={(options) => this.options = options}> { filter.dropdown.map((option, index) => ( this.renderOption(filter, option, index) )) } </ul> and the renderOption function, which obviously renders the list items looks like this: renderOption(filter, option, index) { return ( <li className="prod-group__filter__option" key={index}> <label className="prod-group__filter__option__label"> <input name={filter.name} type="checkbox" defaultValue={option.slug} checked={option.checked} onChange={this.optionChangeHandler} /> {option.label} </label> </li> ); } The value I am trying to alphabetize is option.slug which is coming from a json list. Can anyone help me get a bit closer to rendering this list alphabetically?
It looks like filter.dropdown is your array of options. This array is passed to the .map() method which then runs a renderOption method in the given order. Hence, you should sort the filter.dropdown array in your ul component code just before calling .map() on it.
You will have to sort option alphabetically using plain javascript before calling filter.dropdown.map... on it. I would advice using lodash function _.sortBy(option, 'slug'); where option is an array of objects with a property called slug then you can pass the sorted result to your map function.
In case anyone is interested, the solution was to sort the list items before calling .map() on it as both macbem and finch suggested. I used const to create an "items" constructor that I could then pass into the ul further down: const items = [].concat(this.props.options) .sort((a, b) => { const One = a.slug.toUpperCase(); const Two = b.slug.toUpperCase(); return (One < Two) ? -1 : (One > Two) ? 1 : 0; }) .map((option, index) => this.renderOption(name, option, index)); return ( <div className={classes}> <a className="prod-group__filter__dropdown" ref={(trigger) => this.trigger = trigger} onClick={this.triggerClickHandler}> {label} </a> <ul className="prod-group__filter__options" ref={options => this.options = options}> { items } </ul> </div> );