I apologize for the headline of this question, but I tried to explain the problem as much as I could in one line.
Our school assignment required us to create a (sort of) Tic Tac Toe game but in a certain way. Instead of creating nine different Box elements from the main App
, we were required to create Row
class and Box
class, so the main App will have three Row
class children and each Row
class will have three Box
children.
Now the problem I encountered is how to change the main App state array at a particular element that corresponds to that exact Box component.
I am a beginner and I apologize in advance if I am not seeing something too obvious. If you have any questions, please do ask, I'll be more than happy to explain this as much as I can. Thank you!
import React, { Component } from 'react';
import { render } from 'react-dom';
class App extends Component {
constructor(props) {
super(props);
this.state = {
0 : ['-','-','-'],
1 : ['-','-','-'],
2 : ['-','-','-']
};
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
// if that particular element is '-' change from '-' to 'X'
// else if it is 'X' change to 'O'
// else change to 'X'
}
render() {
const rows = []
for (let i = 0; i < 3; i++) {
rows.push(<Row key = {i} msg = {this.state[i]} handleClick = {this.handleClick}/>)
}
return (
<div>
<div>
<h1>Tic Tac Toe</h1>
</div>
<div id = 'main'>
{rows}
</div>
</div>
);
}
}
class Row extends Component{
render() {
const boxes = []
for (let i = 0; i < 3; i++) {
boxes.push(<Box key = {i} msg = {this.props.msg[i]} handleClick = {this.props.handleClick}/>)
}
return (
<div className = 'row'>
{boxes}
</div>
)
}
}
class Box extends Component{
render() {
return (
<div className = 'box' onClick = {this.props.handleClick}>
<p>{this.props.msg}</p>
</div>
)
}
}
render(<App />, document.querySelector('#root'));
You have organized your components well. You need to handle player switching after each click and updating the values in the game board.
player
(it can just be a boolean as this is a two-player game)this.state = {
0: ["-", "-", "-"],
1: ["-", "-", "-"],
2: ["-", "-", "-"],
player: true
};
handleClick
function like below. handleClick(row, col) {
// set the corresponding value (using row and colum index) in the game borad
this.setState(
{
[row]: this.state[row].map((val, colId) =>
colId === col ? (this.state.player ? "X" : "O") : val
)
},
() => {
// switch the player (toggle the player boolean value)
this.setState({ player: !this.state.player });
}
);
}
row
and col
ids from the click handler in Box
component. <Box
...
...
handleClick={() => {
this.props.handleClick(this.props.row, i);
}}
/>
NOTE: when you develop further to avoid changing the same Box by clicking, you can keep an object to store more info like { value: "-", used: false }
instead of "-"
as values.
Building off of @Amila Senadheera's answer, you could also add a check in the handleClick function to ensure that a player can't overwrite another player's move. Something along the lines of:
handleClick(row, col) {
// set the corresponding value (using row and colum index) in the game board
if(this.state[row][col] !== '-') {
return // Don't allow the player to overwrite an already-marked square.
}
this.setState(
{
[row]: this.state[row].map((val, colId) =>
colId === col ? (this.state.player ? "X" : "O") : val
)
},
() => {
// switch the player (toggle the player boolean value)
this.setState({ player: !this.state.player });
}
);
}
And if you want to add a check for victory, there's some useful discussion here: https://codereview.stackexchange.com/questions/24764/tic-tac-toe-victory-check.
You could define parameters for handleClick like
type T = 1 | 2 | 3;
function handleClick(row: T, col: T) {
const newRow = [ ...this.state[row] ];
this.sign = this.sign === "O" ? "X" : "O";
newRow[col] = this.sign;
this.setState({...this.state, [row]: newRow});
}
and then you need new props for Box:
for (let i = 0; i < 3; i++) {
boxes.push(
<Box
row={props.row}
col={i}
key={i}
msg={this.props.msg[i]}
handleClick={this.props.handleClick}/>
)
}