0

I'm pretty proficient in JavaScript but learning React. I'm trying to make tic-tac-toe. When I get a winner, I want the this.state.message to update immediately, the this.state.done to change to true immediately, and the this.state.turn to not change. Currently I've figured out some of these but not the this.state.turn change. It says that the wrong player wins. I know that setState is asynchronous and that's why I'm having trouble updating these things together, and I fixed some of them with a callback, but I'm trying to find a better way and to generally understand everything better.

class Board extends Component {
    constructor(props) {
        super(props);
        this.state = {
            board: ['', '', '', '', '', '', '', '', ''],
            turn: 'O',
            message: 'O goes',
            done: false
        }
    }
    
    addItem(x) {
        if (this.state.done === true) {
            this.setState({message: this.state.turn + ' wins'});
            return;
        }
        let array = [...this.state.board];
        if (array[x] !== "") {
            return;
        }
        array[x] = this.state.turn;
        this.setState({board: array}, () => {
            this.checkWinners(0, 1, 2);
            this.checkWinners(3, 4, 5);
            this.checkWinners(6, 7, 8);
            this.checkWinners(0, 3, 6);
            this.checkWinners(1, 4, 7);
            this.checkWinners(2, 5, 8);
            this.checkWinners(0, 4, 8);
            this.checkWinners(2, 4, 6);
            if (this.state.done === false) {                                        
                this.setState({turn: (this.state.turn === "X") ? "O" : "X"});
                this.setState({message: (this.state.message === "X goes") ? "O goes" : "X goes"});
            }
        });
    }
    
    checkWinners(a, b, c) {
        if ((this.state.board[a] === "X"
            &&
            this.state.board[b] === "X"
            &&
            this.state.board[c] === "X")
            ||
            (this.state.board[a] === "O"
            &&
            this.state.board[b] === "O"
            &&
            this.state.board[c] === "O"
            )
            ) {
            this.setState({done: true}, () => {
                console.log('Winner');
                this.setState({message: this.state.turn + ' wins'});
            });

            }
    }
    
    render() {
        return (
            <>
            <h1>Tic-Tac-Toe</h1>
            <h2>{this.state.message}</h2>
            <div className="board">
            {this.state.board.map((item, index) => (
                <div className="boxes" key={index} onClick={() => this.addItem(index)}>{this.state.board[index]}</div>
            ))}
            </div>
            </>
        )
    }
}
phershbe
  • 169
  • 4
  • 12
  • Don't try to read the state straight after updating. Setting state is asynchronous. You can't expect it to be up-to-date straight away – evolutionxbox Jul 17 '21 at 23:01
  • @evolutionxbox Thank you for your answer. Maybe I should have asked a different question. I know that setting state is asynchronous but I want to know how I can make all those things change in the same onClick, because if somebody wins I want the game to tell them immediately after they make the move. – phershbe Jul 17 '21 at 23:10
  • Instead of calling `setState()` multiple times, collect your changes first and then call it once. – Robo Robok Jul 17 '21 at 23:13
  • @RoboRobok Thank you. What I still don't understand though is that checking winners depends on the state of the board and then updating or not updating the state of the turn depends on checking winners. I don't understand how to put these all together. – phershbe Jul 17 '21 at 23:27
  • I'd say that your design of having the `checkWinners()` method based on the line is rather poor. It should check all possibilities itself and only continue if necessary. In general, usually in React it's not bad to have this state in-between, because it only affects the UI and having one render cycle being a state in-between usually is fine. – Robo Robok Jul 17 '21 at 23:51

1 Answers1

0

You can try following code just changed the logic of checkWinners function and commented out this.setState({ message: this.state.turn + " wins" }); line. [note: changed some css to test in local]

   constructor(props) {
        super(props);
        this.state = {
            board: ["", "", "", "", "", "", "", "", ""],
            turn: "O",
            message: "O goes",
            done: false,
        };
    }

    addItem(x) {
        if (this.state.done === true) {

            // this.setState({ message: this.state.turn + " wins" });
            return;
        }
        let array = [...this.state.board];
        if (array[x] !== "") {
            return;
        }
        array[x] = this.state.turn;
        this.setState({ board: array }, () => {
            this.checkWinners(0, 1, 2);
            this.checkWinners(3, 4, 5);
            this.checkWinners(6, 7, 8);
            this.checkWinners(0, 3, 6);
            this.checkWinners(1, 4, 7);
            this.checkWinners(2, 5, 8);
            this.checkWinners(0, 4, 8);
            this.checkWinners(2, 4, 6);
            if (this.state.done === false) {
                this.setState({ turn: this.state.turn === "X" ? "O" : "X" });
                this.setState({
                    message:
                        this.state.message === "X goes" ? "O goes" : "X goes",
                });
            }
        });
    }

    checkWinners(a, b, c) {
        if( this.state.board[a] === "X" &&
            this.state.board[b] === "X" &&
            this.state.board[c] === "X"){
            this.setState({ done: true }, () => {
                console.log("Winner");
                this.setState({ message: "X wins" });
            });
        }else if(this.state.board[a] === "O" &&
            this.state.board[b] === "O" &&
            this.state.board[c] === "O"){
            this.setState({ done: true }, () => {
                console.log("Winner");
                this.setState({ message: "O wins" });
            });
        }
    }

    render() {
        return (
            <>
                <h1>Tic-Tac-Toe</h1>
                <h2>{this.state.message}</h2>
                <div className="board container" style={{height:"500px",width:"500px"}}>
                    <div className={"row"} style={{minHeight:"80%"}}>
                        {this.state.board.map((item, index) => (
                            <div
                                className="col-3 bg-secondary m-1"
                                key={index}
                                onClick={() => this.addItem(index)}
                            >
                                {this.state.board[index]}
                            </div>
                        ))}
                    </div>
                </div>
            </>
        );
    }
monesul haque
  • 351
  • 1
  • 11