Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - Create nested components with loops

I have a little issue with React. I can't create a nested component with a for loop. What I want to do is create 9 cells of a table and then create 3 rows with 3 cells for every row and after that mount the 3 rows together and create a board 9x9.

Let say that I want to get something like this, but using a loop

class Board extends React.Component {     
renderSquare(i) {
    return <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />;
}

render(){    
    return(
        <div>
            <div className="board-row">
                {this.renderSquare(0)}
                {this.renderSquare(1)}
                {this.renderSquare(2)}
            </div>
            <div className="board-row">
                {this.renderSquare(3)}
                {this.renderSquare(4)}
                {this.renderSquare(5)}
            </div>
            <div className="board-row">
                {this.renderSquare(6)}
                {this.renderSquare(7)}
                {this.renderSquare(8)}
            </div>
        </div>
    );        
}

}

I searched others question for hours and I think my code is almost correct but it does not render what I want. I only get a white page.

here is my code:

class Board extends React.Component { 

renderSquare(i) {
    return <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />;
}

createCells(i){
    if(i%3){return;}
    var index = this.fillN(Array(i)); //index=[0,1,2,3,4,5,6,7,8]
    var cells = [];
    index.forEach(function(i){
        cells.push(() => {
            return(
                <div>
                    {this.renderSquare(i)}
                </div>
            );
        });
    });
    return cells;
}

createRows(cells){
    var index = this.fillMod3(Array(3)); //index=[0,3,6]
    var rows = []
    index.forEach(function(i){
        rows.push(() => {
            return(
                <div>
                    {cells[i]}
                    {cells[i+1]}
                    {cells[i+2]}
                </div>
            );
        });
    });
    return rows;
}

render(){    
    var cells = this.createCells(9);
    var rows = this.createRows(cells);
    var board = [];
    var index = this.fillN(Array(1));

    index.forEach(function(row){
        board.push(() => {
            return(
                <div>{row}</div>
            );
        });
    })

    return(
        <div>
            {board[0]}
        </div>
    );        
}

I always get on the screen something like this:

<Board>
  <div> /*empty*/ </div>
</Board>

I want to clarify that I am sure that the rest of the code with which that component (Board) interacts has no issues.

I am new in react and if someoane can help me i will apreciate very much. Sorry for my poor English

EDIT1: following marklew examples i should be able to do something like this

    render(){   
    var index1 = this.fillN(Array(3)); //index1=[0,1,2]
    var index2 = this.fillN(Array(3)); //index2=[0,1,2]

    return(
        <div>
            {index1.map((e1,i1) => {
                return(
                    <div key={i1} className="board-row">
                        {index2.map((e2, i2) => {
                            return(
                                <p key={i2+10}>
                                    {this.renderSquare(i2)}
                                </p>
                            )
                        })}
                    </div>
                )    
            })}
        </div>
    );

}

but it doesn't do what I want. I obtain just a column with 9 cells and the cells are the same objects. I dont understand why. (I understand that are the same objects because i assign a handle function onClick when I create them like that:

<Board 
     onClick={(i) => this.handleClick(i)} //handleClick just draws a X in the cell
     />

and I get the X drown in 3 cells simultaneously

EDIT2: I reached a solution:

render(){   
    var index1 = this.fillMod3(Array(3));        

    return(
        <div>
            {index1.map((e,i) => {
                return(
                    <div key={i} className="board-row">
                        {this.renderSquare(e)}
                        {this.renderSquare(e+1)}
                        {this.renderSquare(e+2)}
                    </div>
                )    
            })}
        </div>
    );

}

}

but is not what I want. I want another loop even for the intern renderSquare(i) function.

like image 354
neboduus Avatar asked Jan 15 '17 23:01

neboduus


People also ask

How do you loop over a component in React?

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.

How do you repeat over array and create elements in React?

To loop through an array of objects in React:Use the map() method to iterate over the array. The function you pass to map() gets called for each element in the array. The method returns a new array with the results of the passed in function.


5 Answers

To render a list of elements inside JSX, you can do something like that:

render() {
    return <div>
        {
            [1,2,3].map ( (n) => {
                return this.renderSquare(n)
            })

        }
    </div>;
}   

Just wrap your array of components into {} in your JSX.

To clarify a bit, this is the same logic of:

return <div>
    {
        [
            <h1 key="1">Hello 1</h1>,
            <h1 key="2">Hello 2</h1>,
            <h1 key="3">Hello 3</h1>
        ]           
    }
</div>;

Note that everytime you render an array of components, you must provide a key prop, as pointed here.

Also, if you want simply print row value in your render function, you should replace:

index.forEach(function(row){
    board.push(() => {
        return(
            <div>{row}</div>
        );
    });
})

with:

index.forEach( (row, index) => {
    board.push(<div key={index}>{row}</div>)
})

or, yet, replacing forEach and push with map:

board = index.map( (row, index) => <div key={index}>{row}</div> )

EDIT I created a fiddle with a 9x9 board using your code as a base: https://jsfiddle.net/mrlew/cLbyyL27/ (you can click on the cell to select it)

like image 124
mrlew Avatar answered Oct 22 '22 21:10

mrlew


I see you too are doing the JS React tutorial! Here's what I did, but I'm working on this because there has to be a good way to give each of these individual keys.

I ran into the same issue you were running into where the X's were drawn into 3 squares, and the reason that happens is because when you were rendering squares, some of the "i's" were duplicated. So there were multiple Squares with the "i" of 2 for example (at least that was the case in my issue).

So each Square has an index right? [0,1,2,3,4,5,6,7,8].

First we need to find how these are related in a way that we can render them into rows! So, in your example you have index1 and index2, which I assume will refer to the x and y coordinates of the Square in the Board. Re-writing the array above we come to: [{0,0}, {1,0}, {2,0}, {0,1}, {1,1}, {2,1}, {0,2}, {1,2}, {2,2}] using {x,y} format. How can we use these values (which we would get from your index1 and index2 in order to get the original array of values that we started with [0,1,2,3,4,5,6,7,8]?

What I did was 3 * x + y (or in a loop, 3 * i + j). This way each square has a unique "i" value associated with it.

After I set up my loops I used this SO post to correctly return the elements I created from a separate function (in a poor attempt to keep my code cleaner).

This is what I ended up with, but I need to make sure to set the keys correctly which is actually how I stumbled onto this post:

createSquares() {
  let rows = [];
  for(var i = 0; i < 3; i++){
    let squares = [];
    for(var j = 0; j < 3; j++){
      squares.push(this.renderSquare(3*i+j));
    }
    rows.push(<div className="board-row">{squares}</div>);
  }
  return rows;
}

Then my render function in Board looks like this:

render() {
  return (
    <div>
      {this.createSquares()}
    </div>
  );
}
like image 37
mrzepka Avatar answered Oct 22 '22 19:10

mrzepka


Here is the best I could think of after reading answers here and in Loop inside React JSX :

  render() {
    return (
      <div>
        {
          [...Array(3)].map((_, i) => (
            <div key={i} className="board-row">
              {
                [...Array(3)].map((_, j) => this.renderSquare(3 * i + j))
              }
            </div>
          ))
        }
      </div>
    );
  }

P.S. I'm also doing the challenges in the new React Tutorial. =p

like image 6
fasfsfgs Avatar answered Oct 22 '22 20:10

fasfsfgs


Live demo on codepen

Nested loops in render() function (explanation in comments:)

class Board extends React.Component {
  renderSquare(i) {
    return (
      <Square
        value={this.props.squares[i]}
        key={i}
        onClick={() => this.props.onClick(i)}
      />
    );
  }

  render() {
     var self = this;
    return (
      <div>
      // you can use: [...Array(3)] instead of [1,2,3]
        {[1, 2, 3].map(function(row, rowIdx) { // create rows 
          return (
            <div className="board-row" key={rowIdx}>
              {
              // you can use: [...Array(3)] instead of [1,2,3]
              [1, 2, 3].map((col, colIdx) => { // create columns
                return self.renderSquare((3 * rowIdx) + colIdx); // calculate square index
              })
              }
            </div>
          );
        })}
      </div>
    );  
  }
}
like image 3
Legends Avatar answered Oct 22 '22 21:10

Legends


I will assume you are looking at the React Tutorial example.. As the instructions explicitly point out, the idea is to make two loops, not just the one.

The first loop in this example creates the 3 rows. The second nested loop creates the 3 necessary columns, returning a Square for each position through the renderSquare function. You may notice that I use the indexes of both loops to correctly assign the corresponding index to the square.

return (
    <div>
      {[0,1,2].map(i => {
        return (
          <div className="board-row">
            {[0,1,2].map(j => {
              return this.renderSquare(3*i + j)
            })}
          </div>
        );
      })}
    </div>
  );
like image 1
Mateo Marin Avatar answered Oct 22 '22 19:10

Mateo Marin