Search code examples
javascriptreactjsreact-nativereact-reduxtic-tac-toe

Undo Button in Tic tac toe app Using ReactJS


I am trying to build an undo button in tic tac toe app built using reactJS for which I followed a tutorial in Youtube:

Following is my file: App.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';

import './App.css';

import Status from'./components/Status';

class App extends Component {

  constructor(props){

    super(props)

    this.state = {

      board : Array(9).fill(null),
      player : null,
      winner : null,
      isUndoRedo: false
    }
  }

  checkWinner(){

    let winLines =
      [
        ["0", "1", "2"],
        ["3", "4", "5"],
        ["6", "7", "8"],
        ["0", "3", "6"],
        ["1", "4", "7"],
        ["2", "5", "8"],
        ["0", "1", "3"],
        ["0", "4", "8"],
        ["2", "4", "6"]
      ]

    this.checkmatch(winLines)
  }

  checkmatch(winLines){
    for (let index = 0; index < winLines.length; index++) {
      const [a,b,c]=winLines[index];
      let board = this.state.board;
      if(board[a] && board[a] === board[b] && board[a] === board[c]){
        alert('You won!');
        this.setState({
          winner : this.state.player
        })
      }
    }
  }

  handleClick(index){

    if(this.state.player && !this.state.winner){

      let newBoard = this.state.board

      if(this.state.board[index]===null){

        newBoard[index] = this.state.player

        this.setState({
          board: newBoard,
          player: this.state.player==="X" ? "O" : "X"
        })

        this.checkWinner()

      }
    }
  } 

  setPlayer(player){
    this.setState({player})

  }

  renderBoxes(){
    return this.state.board.map(
      (box, index) => 
      <div className="box" key={index} 
        onClick={()=> {this.handleClick(index)}}>
        {box}
      </div>
    )
  }

  reset(){

    this.setState({
      board : Array(9).fill(null),
      player :  null,
      winner : null

    })

  } 

  undo = (e) => { //Code for undoing the last move
    e.preventDefault();
    this.props.undoRedo.undo();
    this.setState({
      isUndoRedo: true,
    });
  };

  render() {

    return (

      <div className="container">
        <h1>Tic Tac Toe App</h1>

        <Status 
          player={this.state.player} 
          setPlayer={(e) => this.setPlayer(e)}
          winner = {this.state.winner}
        />

        <div className="board">

          {this.renderBoxes()}

        </div>

        <button className='reset' disabled ={!this.state.winner} onClick = {() => this.reset()}> Reset </button>
        <button className='reset' onClick = {this.undo}> Undo </button>

      </div>

    );
  }
}

App.propTypes = {
  undoRedo: PropTypes.object.isRequired, 
  val: PropTypes.string.isRequired,
  update: PropTypes.func.isRequired,
};

export default App;

I followed this link on adding an Undo button, but it shows an error whenever I click the Undo button stating that

TypeError: Cannot read property 'undo' of undefined

for the code this.props.undoRedo.undo();. I have attached the screenshot here Is this the right way of implementing UNDO button in ReactJS so that the user can UNDO the last move? If not can anyone suggest me a better way to achieve this? I am super new to ReactJS and I am learning it, please pardon me if this is a stupid question.


Solution

  • From the documentation I understand that you need to call this.props.undoRedo.addStep in handleClick first and then when you click on undo the undo will work. In your handleClick do this

    handleClick(index){    
        if(this.state.player && !this.state.winner){    
          let newBoard = this.state.board    
          if(this.state.board[index]===null){    
            newBoard[index] = this.state.player    
            this.setState({
              board: newBoard,
              player: this.state.player==="X" ? "O" : "X"
            })    
            this.checkWinner()    
          }
        }
        setTimeout(() => {
           this.props.undoRedo.addStep();
        });
      }