Reputation: 59
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";
}
};
Game Working link with restart new game: https://codepen.io/damodar-bhattarai/pen/zYOBexp?editors=0010
Upvotes: 2
Views: 543
Reputation: 151
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.
Upvotes: 0
Reputation: 342
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>
Upvotes: 1
Reputation: 1169
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>
Upvotes: 2