Search code examples
reactjstic-tac-toe

Tic Tac Toe game in React reset button doesn't work


class App extends React.Component {
  constructor() {
    super();
    this.state = {
      currentPlayer: false, //false is X, true is O
      message: null,
      gameOver: false,
      grids : {
        '0': null,
        '1': null,
        '2': null,
        '3': null,
        '4': null,
        '5': null,
        '6': null,
        '7': null,
        '8': null
    }
  }
  this.baseState = this.state
   
}
  
  restartGame = () => {
    this.setState(this.baseState);
  }
  
updateGridHandler = (index) => {
  let updatedGrids = {...this.state.grids};
  updatedGrids[index] = this.state.currentPlayer;
  this.setState(prevState => ({currentPlayer: !prevState.currentPlayer,
     grids: updatedGrids}),
     () => {
        this.checkDraw();
        this.checkWin();
       });
}
  
  checkWin = () => {
    const winningCombination = [
      [0, 1, 2],
      [3, 4, 5],
      [6, 7, 8],
      [0, 3, 6],
      [1, 4, 7],
      [2, 5, 8],
      [2, 4, 6],
      [0, 4, 8],
    ];

    for (let i = 0; i < winningCombination.length; i++) {
    const [a, b, c] = winningCombination[i];
    if (this.state.grids[a] && this.state.grids[b] &&
       this.state.grids[c]) {
         console.log('O won');
       }
      else if (this.state.grids[a] === false &&
        this.state.grids[b] === false &&
           this.state.grids[c] === false) {
             console.log('X won');
    }
  }
  }
  
    checkDraw = () => {
    for(let grid in this.state.grids)
            if(this.state.grids[grid] === null) return false; 
  }
  
  render() {
   
    return (
      <div className="App">
             <header className="Header">
          <h1>Tic Tac Toe</h1>
          <Board grids={this.state.grids}
            player={this.state.currentPlayer}
            updateGrid={(index) => this.updateGridHandler(index)}
             />
               <Button reset={this.restartGame}/>
        </header>
      </div>
    );
  }
}

class Board extends React.Component {
    clickHandler = (index, event) => {
      if (this.props.grids[index] === null) {
        //update function callbacks
          this.props.updateGrid(index);

          }
  }
  
  hoverNextPlayer = (index, event) => {
    let classesForCircle = ["Grid", "Circle"];
    let classesForX = ["Grid", "X"];
      if (this.props.grids[index] === null) {        
        if (this.props.player) {
          event.target.className = classesForCircle.join(' ')
         } else {
        event.target.className = classesForX.join(' ') 
         }
      }
    }
  
  stopHoverNextPlayer = (index, event) => {
      if (this.props.grids[index] === null) {
        event.target.className = "Grid";
      }
    }


render() {
  let grids = Object.values(this.props.grids)
  .map((value, index) => <Grid 
                           clicked={(event) => this.clickHandler(index, event)}
                            hovered={(event) => this.hoverNextPlayer(index, event)}
                           stopHovered={(event) => this.stopHoverNextPlayer(index, event)}
                           key={index} value={value}
                           currentPlayer={this.props.player}/>
  );

  return (
    <div className="Board" >
      {grids}
    </div>
);
}

}

const Grid = (props) => {
    return (
      <div 
        onClick={props.clicked}
        className="Grid"
        onMouseOver={props.hovered}
        onMouseLeave={props.stopHovered}>
        {props.value}
      </div>
    );

  }

const Button = (props) => {
  return (
    <div>
      <button onClick={props.reset}>Restart
      </button>

    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('main'));
.App {
  text-align: center;
}

.Header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white
}

.App-link {
  color: #61dafb;
}

.Board {
  padding: 5px;
  margin: auto;
  width: 270px;
  height: 270px;
  display: grid;
  justify-content: center;
  grid-template-columns: repeat(3, auto);
}

:root {
    --grid-size: 90px;
    --grid-content: calc(var(--grid-size) * 0.9);
  }

.Grid {
  box-sizing: border-box;
  width: var(--grid-size);
  height: var(--grid-size);
  border: 1px solid #000;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
}


.Grid:hover {
  border: 3px solid black;
}

.Grid.X::before,
.Grid.X::after
 {
    position: absolute;
    content: '';
    background-color: lightgrey;
  width: calc(var(--grid-content)*0.1);
  height: var(--grid-content);
}

.Grid.X::before {
    transform: rotate(45deg);
}

.Grid.X::after {
    transform: rotate(-45deg);
}

.Grid.Circle::before,
.Grid.Circle::after {
    position: absolute;
    content: '';
    border-radius: 50%;
}

.Grid.Circle::before {
    width: calc(var(--grid-content)*0.9);
    height: calc(var(--grid-content)*0.9);
    background-color: lightgrey;
}

.Grid.Circle::after {
    width: calc(var(--grid-content)*0.7);
    height: calc(var(--grid-content)*0.7);
    background-color: #282c34;
}
<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="main"></div>

I am building a Tic Tac Toe game. It all works fine except the restart button. Onclick I am resetting state to my initial state, but my grid component does not update on props change. It gives a hover effect onto my Grid to show next players sign on hover...

When the player wins or it is a draw a Button component gets rendered. Onclick it sets the state to initial state. So all grids state: null but it doesn't update the UI (doesn't remove the classname X or circle). In Grid component {props.value} updates as well to null.All the grids show the last game's last position until I hover over them or hover them out. That is how I can clear the UI back to empty grids. How do I get the Grid component to update accordingly to the props change?

Thanks!


Solution

  • It took me quite some time to figure our, but actually this was wrong:

    Object.values(this.props.grids)
      .map((value, index) => <Grid value={value}/>
      );
    

    Because I cannot map through an object like that. This is correct:

    Object.keys(this.props.grids)
      .map((gridKey, index) => <Grid value={this.props.grids[gridKey] />
    

    with this in place and adding a condional statement in my Grid component

    <div className={(props.value === null) ?
        blank : (props.value === true) ?
        classesForCircle.join(' ') : classesForX.join(' ')
      }>...</div>
    

    It works how it supposed to with the restart button.