Aanchal Adhikari
Aanchal Adhikari

Reputation: 303

Implementation of two for loops in React Js to create a 3X3 square marix box for tic-tac-toe

I'm following the React Js tutorial from the official site which helps us build a tic-tac-toe game. The square boxes are created by hardcoding all the squares as follows:

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 managed to shorten the code by using a for loop as shown below:

render(){
  let sqrRen = [];
  for(let i = 0; i < 9; i=i+3){
    sqrRen.push(<div className = "board-row">
      {this.renderSquare(0+i)}
      {this.renderSquare(1+i)}
      {this.renderSquare(2+i)} 
    </div>);
  }
  return (
    <div>
        {sqrRen}
    </div>
  );
}

But I also want to generate the squares in each row using another for loop as follows:

render(){
  let sqrRen = [];
  for(let i = 0; i < 9; i=i+3){
    sqrRen.push(<div className = "board-row">
      {
        for(let j=0;j<3;j++){
        this.renderSquare(j+i)
        }
      }
    </div>);
  }

  return (
    <div>
      {sqrRen}
    </div>
  );
}

but this is not working. I get the following error: error snippet

Any suggestions on how to go about using two for loops?

Upvotes: 8

Views: 4596

Answers (8)

KaMZaTa
KaMZaTa

Reputation: 741

Here's a solution using functional components:

  const rowCount = 3, colCount = 3;

  return (
    <>
      <div className="status">{status}</div>
      <div>
      {[...new Array(rowCount)].map((x, rowIndex) => {
        return (
          <div className="board-row" key={rowIndex}>
            {[...new Array(colCount)].map((y, colIndex) => {
                const position = rowIndex * colCount + colIndex
                return <Square key={position} value={squares[position]} onSquareClick={() => handleClick(position)} />
              }
            )}
          </div>
        )
      })
      }
    </div>

Upvotes: 0

pasha_gord
pasha_gord

Reputation: 11

This is my attempt at this tutorial. Some of the other solutions generated a warning that no key was present for each of the squares. I attempted to fix that here. I also split out rendering of the row.

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

  renderRow(rowIndex) {
    return (
      <div key={rowIndex} className="board-row">
        {[...new Array(3).keys()].map(colIndex => {
          return this.renderSquare(rowIndex * 3 + colIndex)
        })}
      </div>
    );
  }

  render() {
    return (
      <div>
        {[...new Array(3).keys()].map(rowIndex => {
          return this.renderRow(rowIndex)
        })}
      </div>
    );
  }
}

Upvotes: 0

Alim Bolar
Alim Bolar

Reputation: 531

Here's my attempt at this react tutorial, in case it helps someone. I would appreciate if someone could suggest another method and the logic applied as I am pretty sure there are other better methods than the one below that I am suggesting.

class Board extends React.Component {
  constructor(props) {
    super(props);
  }

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

  renderBoard = () => {
    let squareRows = [];
    let count = 0;
    for (let x = 0; x < 3; x++) {
      const squareCols = [];
      for (let y = 0; y < 3; y++) {
        squareCols.push(this.renderSquare(count));
        count++;
      }
      squareRows.push(squareCols);

    }
    return squareRows;
  };

  render() {
    return (
      <div>
        {this.renderBoard().map((squareRow, index) => {
          return (
            <div key={index} className="board-row">
              {squareRow.map((square) => square)}
            </div>
          );
        })}
      </div>
    );
  }
}

Upvotes: 0

user9844377
user9844377

Reputation:

You have to create the element structure first, then pass it to the container element.

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

fillBoard=()=>{
    //make an empty array for storing the rows
    let rows =[];
    //a function scoped counter for keeping track of current square
    let counter = 0;
    for(let x = 0; x<3; x++){
        //create an array to store each child of board-row (square)
        let rowSquares = [];
        for(let y = 0;y<3;y++){
            //fill the second array with the children (squares)
            rowSquares.push(this.renderSquare(counter));
            //increase function scoped variable to track current square
            counter++;
        }
    //push each row object into the first array with the children array included
     rows.push(<div className="board-row" key={x}>{rowSquares}</div>)
    }
    //return the rows array
    return rows;
}

render() {
    //render the parent container div with its child the rows array
   return(<div>{this.fillBoard()}</div>)
}}

Double for loops can be confusing on their own, adding react to the equation doesn't help. In plain English what you are doing is saying:

  1. Lets make an array to store our row objects
  2. Make a for loop to iterate how ever many rows we need.
  3. Now that we are inside of our first loop, lets make an empty array specific to this iteration of our first row object, where we will store its children.
  4. Create a second for loop to iterate over each child we want to add to this row.
  5. Now that the second loop has added each square to the row parent and is done, we move back into the FIRST loop to finish up by simply adding this completed row object and its children (the squares) to the function scoped array for storing the rows we made before the loops.
  6. When we want to render, we just call that function and put the returned object (our nifty list of rows and squares tree array) inside of our container div.

Upvotes: 1

iamlockon
iamlockon

Reputation: 31

Since I stuck at this additional problem for hours yesterday and finally arrived at this "two For loops" solution, let me share it here for upcoming react learners.

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

  render() {
    const row = [];
    let k = 0; //key
    for (let i = 0; i < 3; i++) {
      const col = [];
      for (let j = 0; j < 3; j++) {
        col.push(this.renderSquare(3*i+j)); //push argument without {}, <>
        k++;
      }
      row.push(<div key={k} className="board-row">{col}</div>);
    }
    return (
      <div>
        {row}
      </div>
    );
  }
}

The most important part is to figure out how to assemble JSX syntax with push. Here "this.renderSquare(3*i+j)" will just return the element, so that's what we want. I personally think this is better than Array.prototype.map solutions.

Upvotes: 3

maki
maki

Reputation: 61

Since I was also looking for this, the accepted answer pointed me to the right direction. Though I made a slightly different implementation without lodash.

render() {
  const rowCount = 3, colCount = 3;
  return (
    <div>
      {[...new Array(rowCount)].map((x, rowIndex) => {
        return (
          <div className="board-row" key={rowIndex}>
            {[...new Array(colCount)].map((y, colIndex) => this.renderSquare(rowIndex*colCount + colIndex) )}
          </div>
        )
      })
      }
    </div>
  );
}

Upvotes: 5

Denialos
Denialos

Reputation: 1006

You don't need to use the usual for loops as they look very messy. Instead, you should utilize the new ES6 features and syntax to achieve a cleaner and more understandable approach.

The solution below renders a 3x3 tic-tac-toe board just fine:

import chunk from 'lodash/chunk';

const styles = { width: '40px', height: '40px', float: 'left', textAlign: 'center' };

return (

        <div className="tic-tac-toe-container">
            {chunk(new Array(9).fill(0), 3).map((item, itemIndex) => {
                return (
                    <div key={itemIndex} className="row">
                        {item.map(col => <div className="col" style={styles}>COL</div>)}
                    </div>
                )
            })
            }
        </div>
    );

Upvotes: 4

Chas Vales
Chas Vales

Reputation: 1

What's the error you're getting in the console? My first guess is you have not defined j in your loop. Try

let j=0

Upvotes: -1

Related Questions