2
votes

I made a tic tac toe game. It works fine but the player's name are static. I have a form with two player's name as an text field which sets state values when typed something.

I am having a problem to render the game when button is clicked.

classNames : Board-> contains the tic tac toe game

Game -> contains Board class with extra divs for "turns" "player names"

StartGame -> contains the form

I have written the following code to get the tic tac toe game div when button is clicked.


<label> Player 1: </label>
<input
  type="text"
  onChange={() => this.setState({ p1: event.target.value })}
  defaultValue={this.state.p1}
  placeholder="Player 1 Name"
/>
<br /> <br />
<label> Player 2: </label>
<input
  type="text"
  onChange={() => this.setState({ p2: event.target.value })}
  defaultValue={this.state.p2}
  placeholder="Player 2 Name"
/>
<br /> <br />
<input
  type="button"
  value="Start New Game"
  onClick={() => {
    this.renderGame(this.state.p1, this.state.p2);
  }}
/>

Code for whole project is: https://codepen.io/damodar-bhattarai/pen/YzKWREN?editors=0010

I want to display the tic tac toe game only when form is filled and button is clicked.

update: Code for renderGame function

  renderGame(p11, p22){
    if (p11 && p22) {
      return <Game p1={p11} p2={p22} />;
    } else {
      return "error";
    }
  };

Final Update

Game Working link with restart new game: https://codepen.io/damodar-bhattarai/pen/zYOBexp?editors=0010

3
Can you show the code for renderGame function.Vipin Yadav
updated at postDamodar Bhattarai
What problem you are facing? In your codepen link, it's seems working fine.Vipin Yadav
I don't want to display tic tac toe until the user inputs player name and clicks on the button. And I want to update player name with the input name from user on turn: X(player 1) or O(player 2)Damodar Bhattarai
For that, you can create some initial Game states, which you can use for user-names, show/hide the tic-tac-toe grid, win/loose and reset after game over.Vipin Yadav

3 Answers

1
votes

it's easier to do this using a boolean to determine if you should render a component. in render game just set a variable to true on the state if both names are filled out.

class StartGame extends React.Component {
  constructor(props){
    super(props);
    this.state={
      p1: '',
      p2:'',
    };

    // so this will refer to the component and not the function
    this.renderGame = this.renderGame.bind(this);
  }
  renderGame(){
    // if p1 and p2 then renderGame should be true and clear any error messages.
    if (this.state.p1 && this.state.p2) {
      this.setState({ renderGame: true, error: '' })
    // else tell the user they need to enter player names.
    } else {
      this.setState({ error: 'Please enter player names.' })
    }

  }
  render() {
    return (
        <div className="game-info">
           {this.state.error} 
           <br/>

           <label> Player 1: </label>
          <input type="text" 
            onChange={() => this.setState({ p1: event.target.value })}
            defaultValue={this.state.p1}
            placeholder="Player 1 Name" /> 

          <br /> <br />
          <label> Player 2: </label>
          <input type="text" 
            onChange={() => this.setState({p2:event.target.value})}
            defaultValue={this.state.p2}
            placeholder="Player 2 Name" /> 
          <br /> <br /> 

          <input type="button" value="Start New Game" onClick={this.renderGame}/>
          // if the boolean is true then go ahead and render the game component
          {this.state.renderGame && <Game p1={this.state.p1} p2={this.state.p2}/>}
        </div>

    );
  }
}

function Square(props) {
  return ( <
    button className = "square"
    onClick = {
      props.onClick
    } > {
      props.value
    } <
    /button>
  );
}

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
      xIsNext: true,
      clicks: 0,
      p1: props.p1,
      p2: props.p2,
    };
  }
  handleClick(i) {
    const squares = this.state.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    let count = this.state.clicks;
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
      clicks: ++count
    });
  }

  renderSquare(i) {
    return <Square
    value = {
      this.state.squares[i]
    }
    onClick = {
      () => this.handleClick(i)
    }
    />;
  }

  render() {
    const winner = calculateWinner(this.state.squares);
    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X(' + this.state.p1 + ')' : 'O(' + this.state.p2 + ')');
    }
    let countclick;
    countclick = this.state.clicks;
    return ( <
      div >
      <
      p > No.of Clicks: {
        countclick
      } < /p> <
      div className = "status" > {
        status
      } < /div> <
      div className = "board-row" > {
        this.renderSquare(0)
      } {
        this.renderSquare(1)
      } {
        this.renderSquare(2)
      } <
      /div> <
      div className = "board-row" > {
        this.renderSquare(3)
      } {
        this.renderSquare(4)
      } {
        this.renderSquare(5)
      } <
      /div> <
      div className = "board-row" > {
        this.renderSquare(6)
      } {
        this.renderSquare(7)
      } {
        this.renderSquare(8)
      } <
      /div> < /
      div >
    );
  }
}

class Game extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      p1: props.p1,
      p2: props.p2,
    };
  }

  render() {
    return ( <
      div className = "game" >
      <
      div className = "game-board" >
      <
      Board p1 = {
        this.state.p1
      }
      p2 = {
        this.state.p2
      }
      /> < /
      div >

      <
      /div>
    );
  }
}
class StartGame extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        p1: '',
        p2: '',
      };

      // so this will refer to the component and not the function
      this.renderGame = this.renderGame.bind(this);
    }
    renderGame() {
      // if p1 and p2 then renderGame should be true and clear any error messages.
      if (this.state.p1 && this.state.p2) {
        this.setState({
          renderGame: true,
          error: ''
        })
        // else tell the user they need to enter player names.
      } else {
        this.setState({
          error: 'Please enter player names.'
        })
      }

    }
    render() {
      return ( <
        div className = "game-info" > {
          this.state.error
        } <
        br / >

        <
        label > Player 1: < /label> <
        input type = "text"
        onChange = {
          () => this.setState({
            p1: event.target.value
          })
        }
        defaultValue = {
          this.state.p1
        }
        placeholder = "Player 1 Name" / >

        <
        br / > < br / >
        <
        label > Player 2: < /label> <
        input type = "text"
        onChange = {
          () => this.setState({
            p2: event.target.value
          })
        }
        defaultValue = {
          this.state.p2
        }
        placeholder = "Player 2 Name" / >
        <
        br / > < br / >

        <
        input type = "button"
        value = "Start New Game"
        onClick = {
          this.renderGame
        }
        />
        
        {
          this.state.renderGame && < Game p1 = {
            this.state.p1
          }
          p2 = {
            this.state.p2
          }
          />} < /
          div >

        );
      }
    }
    // ========================================

    ReactDOM.render( <
      StartGame / > ,
      document.getElementById('root')
    );


    function calculateWinner(squares) {
      const lines = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6],
      ];
      for (let i = 0; i < lines.length; i++) {
        const [a, b, c] = lines[i];
        if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
          return squares[a];
        }
      }
      return null;
    }
body {
  font: 14px "Century Gothic", Futura, sans-serif;
  margin: 20px;
}

ol,
ul {
  padding-left: 30px;
}

.board-row:after {
  clear: both;
  content: "";
  display: table;
}

.status {
  margin-bottom: 10px;
}

.square {
  background: #fff;
  border: 1px solid #999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}

.square:focus {
  outline: none;
}

.kbd-navigation .square:focus {
  background: #ddd;
}

.game {
  display: flex;
  flex-direction: row;
}

.game-info {
  margin-left: 20px;
}
<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="errors" style="
  background: #c00;
  color: #fff;
  display: none;
  margin: -20px -20px 20px;
  padding: 20px;
  white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
  window.addEventListener('mousedown', function(e) {
    document.body.classList.add('mouse-navigation');
    document.body.classList.remove('kbd-navigation');
  });
  window.addEventListener('keydown', function(e) {
    if (e.keyCode === 9) {
      document.body.classList.add('kbd-navigation');
      document.body.classList.remove('mouse-navigation');
    }
  });
  window.addEventListener('click', function(e) {
    if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
      e.preventDefault();
    }
  });
  window.onerror = function(message, source, line, col, error) {
    var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
    errors.textContent += text + '\n';
    errors.style.display = '';
  };
  console.error = (function(old) {
    return function error() {
      errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
      errors.style.display = '';
      old.apply(this, arguments);
    }
  })(console.error);
</script>
2
votes

The simplest solution will be to use a flag to render the component and toggle the flag when the user clicks on the 'Start new Game' button.

Hope the below solution helps. Please reach out to me if you need any clarification.

function Square(props) {
  return ( <
    button className = "square"
    onClick = {
      props.onClick
    } > {
      props.value
    } <
    /button>
  );
}

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
      xIsNext: true,
      clicks: 0,
      p1: props.p1,
      p2: props.p2,
    };
  }
  handleClick(i) {
    const squares = this.state.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    let count = this.state.clicks;
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
      clicks: ++count
    });
  }

  renderSquare(i) {
    return <Square
    value = {
      this.state.squares[i]
    }
    onClick = {
      () => this.handleClick(i)
    }
    />;
  }

  render() {
    const winner = calculateWinner(this.state.squares);
    let status;
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X(' + this.state.p1 + ')' : 'O(' + this.state.p2 + ')');
    }
    let countclick;
    countclick = this.state.clicks;
    return ( <
      div >
      <
      p > No.of Clicks: {
        countclick
      } < /p> <
      div className = "status" > {
        status
      } < /div> <
      div className = "board-row" > {
        this.renderSquare(0)
      } {
        this.renderSquare(1)
      } {
        this.renderSquare(2)
      } <
      /div> <
      div className = "board-row" > {
        this.renderSquare(3)
      } {
        this.renderSquare(4)
      } {
        this.renderSquare(5)
      } <
      /div> <
      div className = "board-row" > {
        this.renderSquare(6)
      } {
        this.renderSquare(7)
      } {
        this.renderSquare(8)
      } <
      /div> < /
      div >
    );
  }
}

class Game extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      p1: props.p1,
      p2: props.p2,
    };
  }

  render() {
    return ( <
      div className = "game" >
      <
      div className = "game-board" >
      <
      Board p1 = {
        this.state.p1
      }
      p2 = {
        this.state.p2
      }
      /> < /
      div >

      <
      /div>
    );
  }
}
class StartGame extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        p1: '',
        p2: '',
        showGame: false
      };
    }

    renderGame(p11, p22) {
      debugger;
      if (p11 && p22) {
        this.setState({
          showGame: true
        });
      }

    }

    render() {
      return ( <
        div className = "game-info" >


        <
        label > Player 1: < /label> <
        input type = "text"
        onChange = {
          () => this.setState({
            p1: event.target.value
          })
        }
        defaultValue = {
          this.state.p1
        }
        placeholder = "Player 1 Name" / >

        <
        br / > < br / >
        <
        label > Player 2: < /label> <
        input type = "text"
        onChange = {
          () => this.setState({
            p2: event.target.value
          })
        }
        defaultValue = {
          this.state.p2
        }
        placeholder = "Player 2 Name" / >
        <
        br / > < br / >

        <
        input type = "button"
        value = "Start New Game"
        onClick = {
          () => {
            this.renderGame(this.state.p1, this.state.p2);
          }
        }
        />

        {
          this.state.showGame && < Game
          p1 = {
            this.state.p1
          }
          p2 = {
            this.state.p2
          }
          />} < /
          div >


        );
      }
    }
    // ========================================

    ReactDOM.render( <
      StartGame / > ,
      document.getElementById('root')
    );


    function calculateWinner(squares) {
      const lines = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6],
      ];
      for (let i = 0; i < lines.length; i++) {
        const [a, b, c] = lines[i];
        if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
          return squares[a];
        }
      }
      return null;
    }
body {
  font: 14px "Century Gothic", Futura, sans-serif;
  margin: 20px;
}

ol,
ul {
  padding-left: 30px;
}

.board-row:after {
  clear: both;
  content: "";
  display: table;
}

.status {
  margin-bottom: 10px;
}

.square {
  background: #fff;
  border: 1px solid #999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}

.square:focus {
  outline: none;
}

.kbd-navigation .square:focus {
  background: #ddd;
}

.game {
  display: flex;
  flex-direction: row;
}

.game-info {
  margin-left: 20px;
}
<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="errors" style="
  background: #c00;
  color: #fff;
  display: none;
  margin: -20px -20px 20px;
  padding: 20px;
  white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
  window.addEventListener('mousedown', function(e) {
    document.body.classList.add('mouse-navigation');
    document.body.classList.remove('kbd-navigation');
  });
  window.addEventListener('keydown', function(e) {
    if (e.keyCode === 9) {
      document.body.classList.add('kbd-navigation');
      document.body.classList.remove('mouse-navigation');
    }
  });
  window.addEventListener('click', function(e) {
    if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
      e.preventDefault();
    }
  });
  window.onerror = function(message, source, line, col, error) {
    var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
    errors.textContent += text + '\n';
    errors.style.display = '';
  };
  console.error = (function(old) {
    return function error() {
      errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
      errors.style.display = '';
      old.apply(this, arguments);
    }
  })(console.error);
</script>
0
votes

I forked your codepen and made the changes.

https://codepen.io/therj/pen/yLBJZJb

  constructor(props){
    super(props);
    this.state={
      p1: '',
      p2:'',
      player_set: false
    };

Introduced a new state player_set.

You were handling onChange inline, I cloned it locally and React threw global-event error. Fixed by creating a handleChange method.

    const handleChange = (event) => {
      this.setState(
        {...this.state,[event.target.name]: event.target.value,
        }, ()=>{
          this.setState({...this.state, 'player_set': this.state.p1 && this.state.p2})
        })
      }

I added name = "p1" and name = "p2", that's how single onChange will set state for both. I changed player_set state in callback tp setState, setting player_set and p1/p2 together might cause issues (I can't confirm, someone can comment on it, maybe?!).

Finally, in the StartGame:

{this.state.player_set &&
           <Game
           p1={this.state.p1}
           p2={this.state.p2}
           />
          }

This will render Game if player_set state is true!

You can use those states for player names as well.