2

I'm new to react.js and I started trying to create a simple Tic-Tac Toe app, but I'm having an issue with my logic.

When I console.log the current turn I have the correct "X" or "O" character tile. However, when I console.log it inside the render() function I get the opposite character tile (i.e. if the winner is "X" then the winner renders on the template as "O").

I tried swapping the value of current value inside the render() function, but it's giving me an error. Any ideas on what I'm doing wrong here?

Link to my github repo is here, or the check out the code is below. https://github.com/jchan922/tic-react-toe/blob/master/src/App.js

Thanks!

EDIT:

  • CodePen

    -- http://codepen.io/jchan922/pen/LxKEwm?editors=0010

  • Question clarification

    -- even though my logic is console logging everywhere else correctly, if the winner is "X" then the winner renders on the view as "O" and vice versa. I can't get my template to render the correct "X" or "O" player.

enter image description here

class App extends Component {
    // Component Initialization
    constructor(props){
        super(props)
        this.state = {
            player_one: "X",
            player_two: "O",
            currentTurn: "X",
            board: [
                "", "", "", "", "", "", "", "", ""
            ],
            winner: null,
            moveCount: 0
        }
    }

    handleClick(index){
        if(this.state.board[index] === "" && !this.state.winner) {
            var currentBoard = this.state.board,
                count = this.state.moveCount;

            currentBoard[index] = this.state.currentTurn;
            count += 1;

            this.setState({
                board: this.state.board,
                winner: this.checkForWinner(),
                currentTurn: this.state.currentTurn === this.state.player_one ? this.state.player_two : this.state.player_one,
                moveCount: count
            });

        }
    }

    handleReset(){
        console.log("RESET")
        this.setState({
            player_one: "X",
            player_two: "O",
            currentTurn: "X",
            board: [
                "", "", "", "", "", "", "", "", ""
            ],
            winner: null,
            moveCount: 0
        })
    }

    checkForWinner(moveCount) {
        var currentTurn = this.state.currentTurn;
        var symbols = this.state.board;
        var winningCombos = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]];

        return winningCombos.find(function(combo) {
            if(symbols[combo[0]] !== "" && symbols[combo[1]] !== ""  && symbols[combo[2]] !== ""  && symbols[combo[0]] === symbols[combo[1]] && symbols[combo[1]] === symbols[combo[2]]) {
                console.log("Found a winner", currentTurn, [combo[0],combo[1],combo[2]]);
                return currentTurn;
            }
            else {
                return null;
            }

        })
    }

    // Component Render
    render() {
        console.log(this.state.currentTurn);
        return (
            <div className="app-container">
                {this.state.winner ? <h2>{`The winner is ${this.state.currentTurn}`}</h2> : null}
                <div className="board">
                    {this.state.board.map((cell, index) => {
                        return <div onClick={() => this.handleClick(index)} key={index} className="square"><p className="playerSymbol">{cell}</p></div>
                    })}
                </div>
                <div className="reset">
                    <button onClick={() => this.handleReset()} className="resetButton">RESET</button>
                </div>
            </div>
        )
    }
}
export default App;
4
  • Whats the question? Commented Feb 19, 2017 at 8:59
  • @Chris even though my logic is console logging everywhere else correctly, if the winner is "X" then the winner renders on the template as "O" and vice versa. Commented Feb 19, 2017 at 9:09
  • Ah gotcha, let me have a look. Actually, could you chuck it in a codepen? Makes it easier to debug Commented Feb 19, 2017 at 9:19
  • @Chris, here ya go! codepen.io/jchan922/pen/LxKEwm?editors=0010 Commented Feb 19, 2017 at 10:33

2 Answers 2

1

First you don't need player_one and player_two in your state, just have them as constants, in case you want to have player_one as 'O' sometimes I prefer to have a boolean props that controls who plays first and let the parent component handle them(unless App is the only component that renders but I don't see anything in your UI that chooses the first one to play).

Secondly since moveCount isn't rendered at all why do you have it in your state ? maybe you want to add some rendering for it later.

Now to your problem, let's pretend that it's 'X' turn and that the player clicked the winning click, 'X' won but before that the click fires your handleClick function, before the last statement this.setState(...) currentturn equals this.state.player_one which is 'X' but you do this :

this.setState({... , currentturn : this.state.currentturn === this.state.player_one ? this.state.player_two : this.state.player_one,...});

Which means "if currentturn is X then set it to O , otherwise set it to X", after that because state changed the UI rerenders with currentturn='O' , now in your render function you put the winner is {currentturn} so it renders the winner is O.

The solution is simple, just replace {currentturn} with {currentturn===player_one ? player_two : player_one}, do this change only where you declare the winner.

Here is the codepen updated : http://codepen.io/abnud1/pen/KajpWN?editors=0010

Sign up to request clarification or add additional context in comments.

4 Comments

thanks! you're correct on the first two points. however, this solution only seems to work for X and not O. Since i'm very new to react, is it best practice to do this logic in the render section? I also tried moving around some of the keys in the this.setState({...}) to see if order matters in the handleClick() method, but nothing happened.
@justinchanman it worked with 'O' too, you can see the above codepen
the order of keys doesn't matter, that's not react, that's javascript, as for best practice react is a UI library , there isn't really much place for this kind of logic outside render because this is UI logic, what we can talk about is logic outside the scope of React (business logic that is)
thanks so much! I figured out that one of my variables was misspelled, which is why it was only showing up as X when i rendered on the view.
0

If you want something reusable, for example, code which is possible to use if there will be a board of 4x4 or 5x5 instead of 3x3 you need something like this

Just generate a matrix with X and O where X will be true and O will be false

https://gist.github.com/Hovakimyan/342d579aa22a567d84d92798bf2667a6

Here size will be your board SIZE for example 3 | 4 | 5...

  checkMatrixForWin = (matrix) => {
    const angl = [], antiAngl = []
    for (let i = 0; i < SIZE; i++) {
      let rowvalue = null
      for (let j = 0; j < SIZE; j++) {
        if (typeof matrix[j][i] !== 'boolean') break

        if (j === 0) rowvalue = matrix[j][i]

        if (rowvalue !== matrix[j][i]) {
          break
        }
        if (j === SIZE - 1) {
          return matrix[j][i]
        }
      }
      angl.push(matrix[i][i])
      antiAngl.push(matrix[i][SIZE - 1 - i])
      if (matrix[i].every(item => item === matrix[i][0])) {
        return matrix[i][0]
      }
    }
    if (angl.every(item => item === angl[0])) {
      return angl[0]
    }
    if (antiAngl.every(item => item === antiAngl[0])) {
      return antiAngl[0]
    }
  }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.