Search code examples
javascriptreactjsjsxcreate-react-app

How do you change the state in React on a click event?


New to React and used Create React App to create a simple tic-tac-toe game but running into the following problems:

  • Cannot get the "X" and "O" to appear in the DOM after clicking in a square
  • The currentTurn property doesn't change and it constantly remains X's turn

In the below code, if I add a console.log(this.state.board) to the handleClick() function the board array changes but it's all Xs. Any ideas?

Here is my App.js:

import React, { Component } from 'react';
import './App.css';

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      PLAYER_ONE_SYMBOL: "X",
      PLAYER_TWO_SYMBOL: "O",
      currentTurn: "X",
      board: [
        "", "", "", "", "", "", "", "", ""
      ]
    }
  }

  handleClick(index) {
    var this_board = this.state.board;
    this_board[index] = this.state.currentTurn;
    this.setState = ({
      board: this.state.board,
      currentTurn: this.state.currentTurn === this.state.PLAYER_ONE_SYMBOL ? this.state.PLAYER_TWO_SYMBOL : this.state.PLAYER_ONE_SYMBOL
    })
  }

  render() {
    return (
      <div className="board">
        {this.state.board.map((cell, index) => {
          return <div onClick={() => this.handleClick(index)} className="square">{cell}</div>;
        })}
      </div>
    );
  }
}

My App.css:

.board {
  display: flex;
  width: 600px;
  height: 600px;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: flex-start;
}

.square {
  display: flex;
  height: 200px;
  width: 200px;
  box-sizing: border-box;
  border: 5px solid black;
  font-size: 5em;
  justify-content: center;
  align-items: center;
}

.square:hover {
  cursor: pointer;
  background-color: #80cd92;
}

EDIT: Realized I made a dumb mistake by treating this.setState as an expression instead of a function and wrote incorrect syntax. This works:

this.setState({
  board: this.state.board,
  currentTurn: this.state.currentTurn === this.state.PLAYER_ONE_SYMBOL ? this.state.PLAYER_TWO_SYMBOL : this.state.PLAYER_ONE_SYMBOL
})

Solution

  • There are few problems with your code.

    1. You shouldn't keep any values in the state if you're not using it inside the render function. In your case, you aren't using PLAYER_ONE_SYMBOL, PLAYER_TWO_SYMBOL and currentTurn inside render function. So you can define them as normal variables in the file or instance variable of the component class.

    2. setState is a function. You can call it by passing changes of state as an object or function which return changes of state as an object. Read more about this in official documentation.

    3. You shouldn't mutate the previous state. You should always create new object state based on the previous state without mutating it.

    So you can change your App component code to something like following to make it work.

    import React, { Component } from "react";
    import { render } from "react-dom";
    import "./App.css";
    
    const PLAYER_ONE_SYMBOL = "X";
    const PLAYER_TWO_SYMBOL = "O";
    
    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          board: ["", "", "", "", "", "", "", "", ""]
        };
        this.currentTurn = PLAYER_ONE_SYMBOL;
      }
    
      handleClick(index) {
        this.setState({
          board: this.state.board.map(
            (val, boardIndex) => (boardIndex === index ? this.currentTurn : val)
          )
        });
    
        this.currentTurn =
          this.currentTurn === PLAYER_ONE_SYMBOL
            ? PLAYER_TWO_SYMBOL
            : PLAYER_ONE_SYMBOL;
      }
    
      render() {
        return (
          <div className="board">
            {this.state.board.map((cell, index) => {
              return (
                <div onClick={() => this.handleClick(index)} className="square">
                  {cell}
                </div>
              );
            })}
          </div>
        );
      }
    }
    

    Notice how I have changed the state using setState function but without mutating state. map function always returns a new array without modifying/mutating original one.