0

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!

Gabi C
  • 461
  • 3
  • 10
  • 21
  • can you give the code in any kind of online code editor. its easier to work with it – ezio4df Jul 05 '20 at 15:10
  • 1
    `Onclick it sets the state to initial state` I don't see any of that code in your qustion. – HMR Jul 05 '20 at 15:25

2 Answers2

0

The issue out here is maybe because, copy by reference is taking place in this.baseState = this.state, and not copy by value.

So, whenever you are trying to reset the state using baseState, no change is happening since baseState has the latest value of state.

Try this, copy by value:

this.baseState = {...state}

P.S This works only for a shallow copy. If you intend to have a deep copy try one of the options here What is the most efficient way to deep clone an object in JavaScript?

Rohan Agarwal
  • 2,441
  • 2
  • 18
  • 35
  • ```this.baseState = {...state}``` Gives an error: 'state' is not defined ```this.baseState = {...this.state}``` Does the same as the original code. I don't think this is the problem though, because in google chrome dev tools I can see grids get updated and all values are null, after clicking the restart button – Gabi C Jul 06 '20 at 14:54
0

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.

Gabi C
  • 461
  • 3
  • 10
  • 21