React - Create nested components with loops

2019-04-06 15:45发布

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.

7条回答
爷的心禁止访问
2楼-- · 2019-04-06 16:14

I'm working on the React tutorial also. Thanks for your responses. I got to this solution:

render() {
    const rows = [];
    for(let i = 0; i<9; i=i+3) {
        const oneRow = [];
        for(let j = i; j < i+3; j++) {
            oneRow.push(this.renderSquare(j, j))
        }
        rows.push(<div className="board-row" key={i + 'someId'}>{oneRow}</div>)
    }
    return (
        <div>
            {rows}
        </div>
    );
}

Where I changed renderSquare to set a key for the Square component, as the second argument.

renderSquare(i, key) {
    return (
        <Square
            value={this.props.squares[i]}
            onClick={() => this.props.onClick(i)}
            key={key}
        />
    );
}
查看更多
Juvenile、少年°
3楼-- · 2019-04-06 16:15

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)

查看更多
萌系小妹纸
4楼-- · 2019-04-06 16:20

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>
  );
}
查看更多
迷人小祖宗
5楼-- · 2019-04-06 16:23

You are pushing functions to the board array. If you want render them, you have to call those functions like

{board[0]()}

I prepared an example that covers your problem: http://jsbin.com/hofohiy/edit?js,output

查看更多
欢心
6楼-- · 2019-04-06 16:24

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

查看更多
老娘就宠你
7楼-- · 2019-04-06 16:27

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>
    );  
  }
}
查看更多
登录 后发表回答