Access and change the state array element in main React App from grandchild

Solution 1:

You have organized your components well. You need to handle player switching after each click and updating the values in the game board.

  1. Add another state variable called player (it can just be a boolean as this is a two-player game)
this.state = {
      0: ["-", "-", "-"],
      1: ["-", "-", "-"],
      2: ["-", "-", "-"],
      player: true
    };
  1. You can pass row and col index to handleClick function like below.
  handleClick(row, col) {
    // set the corresponding value (using row and colum index) in the game borad
    this.setState(
      {
        [row]: this.state[row].map((val, colId) =>
          colId === col ? (this.state.player ? "X" : "O") : val
        )
      },
      () => {
        // switch the player (toggle the player boolean value)
        this.setState({ player: !this.state.player });
      }
    );
  }
  1. Pass the row and col ids from the click handler in Box component.
       <Box
          ...
          ...
          handleClick={() => {
            this.props.handleClick(this.props.row, i);
          }}
        />

Code Sandbox

NOTE: when you develop further to avoid changing the same Box by clicking, you can keep an object to store more info like { value: "-", used: false } instead of "-" as values.

Solution 2:

You could define parameters for handleClick like

type T = 1 | 2 | 3;
function handleClick(row: T, col: T) {
  const newRow = [ ...this.state[row] ];
  this.sign = this.sign === "O" ? "X" : "O";
  newRow[col] = this.sign;
  this.setState({...this.state, [row]: newRow});
}

and then you need new props for Box:

for (let i = 0; i < 3; i++) {
  boxes.push(
    <Box 
      row={props.row}
      col={i}
      key={i} 
      msg={this.props.msg[i]} 
      handleClick={this.props.handleClick}/>
  )
}

Solution 3:

Building off of @Amila Senadheera's answer, you could also add a check in the handleClick function to ensure that a player can't overwrite another player's move. Something along the lines of:

handleClick(row, col) {
    // set the corresponding value (using row and colum index) in the game board
    if(this.state[row][col] !== '-') {
      return // Don't allow the player to overwrite an already-marked square.
    }
    this.setState(
      {
        [row]: this.state[row].map((val, colId) =>
          colId === col ? (this.state.player ? "X" : "O") : val
        )
      },
      () => {
        // switch the player (toggle the player boolean value)
        this.setState({ player: !this.state.player });
      }
    );
  }

And if you want to add a check for victory, there's some useful discussion here: https://codereview.stackexchange.com/questions/24764/tic-tac-toe-victory-check.