I am going through the Reactjs doc and I tried adding a little feature to the tic-tac-toe game in the tutorial of the documentation. In the helper function that tells if a player won, there was no way to tell if the game ended as a draw. so I added this feature, however, there is a bug in my code, so that when the 9 squares are complete, and there is obviously a winner, sometimes it shows that there is a winner and sometimes it shows a draw. Please help to figure out this bug. Thank you.
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,
}
}
handleClick(i){
//console.log(this.state.counter)
const squares = this.state.squares.slice();
if(calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({squares: squares, xIsNext: !this.state.xIsNext,})
}
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' : 'O');
}
return (
<div>
<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 {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
// ========================================
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Game />);
function calculateWinner(squares, counter) {
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];
const squaresFilled = squares.every(value => value !== null);
const threeSquaresEqual = squares[a] && squares[a] === squares[b] && squares[a] === squares[c]
console.log(!threeSquaresEqual)
if(threeSquaresEqual) {
return squares[a];
} else if(squaresFilled && threeSquaresEqual) {
return "Draw";
}
}
return null;
}
Instead of adding the logic at the winner just use a new state in the constructor to count the turns and evaluate if board has been completed without a winner
Add a new state to check on turns count
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
turns: 0
}
}
Make the counter go up on the click event
handleClick(i){
//console.log(this.state.counter)
const squares = this.state.squares.slice();
if(calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
turns: this.state.turns + 1
})
}
Then when rendering, if turns are 9 and winner is null then render "Draw"
render() {
const winner = calculateWinner(this.state.squares);
let status;
if(winner == null && turns == 9) {
status = 'Draw';
} else if(winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
return (<> ... </>)
}
Something like that.
Related
Can someone briefly explain why this keyword is undefined in a function expression, but defined in an arrow function. I'm simply trying to follow the tutorial on the React website and they gloss over a seemingly important concept regarding confusing behavior of this but the link refers to a very in-depth article which is not at all friendly to beginners. After completing the tutorial I wanted to play around and try to understand what works and what doesn't and I tried replacing an arrow function with a function expression, and suddenly the onClick event failed to work.
This is the code as written in the tutorial:
class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => {
this.props.onClick(i);
}} />
);
}
//other stuff
}
And I wanted to change the arrow function to a traditional function expression, but had some problems
class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={function() {
console.log(this); //undefined
console.log(this.props); //throws exception
this.props.onClick(i); //throws exception
}} />
);
}
}
Why is this undefined in the function expression? When I read the documentation for arrow function and function expressions on MDN it seems that the opposite should be true. What's going on here?
Here the codepen
Here's the full project:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i) } />
);
}
render() {
return (
<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 = {
isX: true,
history: [{
squares: Array(9).fill(null)
}],
stepNumber: 0
}
}
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
const current = this.state.history[history.length - 1];
const squares = current.squares.slice();
if (squares[i] == null) {
squares[i] = this.state.isX ? 'X' : 'O';
} else {
console.log("Square [" + i + "] is already marked!");
return;
}
this.setState({
history: history.concat([{squares: squares}]),
isX: !this.state.isX,
stepNumber: history.length
});
}
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0,
});
}
render() {
const history = this.state.history;
const current = this.state.history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.isX ? 'X' : 'O');
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}
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;
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
I am trying to create a simple tic tac toe gaming using functional components. However, I seem to have an issue where my state is always initially one step behind on the first click of a square. I know this question has been asked many times and I have read through a lot of posts, but I am still struggling to get my state in sync. I also noticed that when I call my board reset function instead of starting with X as it should it tries to start with O which technically should not be possible since it is being set back to its initial state. Any help or suggestions anyone has would be greatly appreciated!
App.js
import React, { useState, useEffect } from 'react';
import Board from './Board/Board';
import { calculateWinner } from '../shared/utils';
import { DEFAULT_BOARD } from '../shared/constants';
import './App.css';
const App = () => {
const [state, setState] = useState({
squares: Array(9).fill(null),
stepNumber: 0,
xIsNext: true,
status: "Next player: X",
moves: 0
});
useEffect(() => {
const winner = calculateWinner(state.squares);
if(winner) {
setState((prevState) => ({
...prevState,
status: 'Winner: ' + winner
}));
}
if(state.moves >= 9) {
setState((prevState) => ({
...prevState,
status: 'Its a draw!'
}));
}
}, [state.squares, state.moves]);
const resetBoard = () => {
setState({...DEFAULT_BOARD});
}
const handleClick = (i) => {
const tempSquares = state.squares.slice();
if (calculateWinner(tempSquares) || tempSquares[i]) {
return;
}
let temp = !state.xIsNext
tempSquares[i] = state.xIsNext ? 'X' : 'O';
setState((prevState) => ({
...prevState,
xIsNext: temp,
moves: state.moves + 1,
squares: tempSquares,
status: 'Next player: ' + (state.xIsNext ? "X" : "O")
}))
}
return (
<div className="game">
<Board handleClick={handleClick} status={state.status} squares={state.squares} resetBoard={resetBoard}/>
</div>
);
}
export default App;
Board.js
import Square from '../Square/Square';
import './Board.css';
const Board = ({status, handleClick, squares, resetBoard}) => {
const renderSquare = (i) => {
return (
<Square value={squares[i]} onClick={handleClick} index={i} />
);
}
return (
<Fragment>
<div className="board">
<div className="status">{status}</div>
<div className="board-grid">
{squares.map((value, index) => {
return (
<div key={index}>
{renderSquare(index)}
</div>
)
})}
</div>
<button onClick={resetBoard}>Reset Board</button>
</div>
</Fragment>
)
}
export default Board;
Square.js
import React from 'react';
import './Square.css'
const Square = ({onClick, value, index}) => {
return (
<button className="square" onClick={() => onClick(index)}>
{value}
</button>
)
}
export default Square;
util.js
export const 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;
}
constants.js
export const DEFAULT_BOARD = {
squares: Array(9).fill(null),
xIsXNext: true,
status: "Next player: X",
moves: 0
}
I'm totally new to React and now I'm following react tutorial which makes a tic-toe game. I'm trying to follow it too. The problem I have is that render method is called twice and I don't know why.
Board Component
export class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
Xturn: true,
};
}
changeTurn(i) {
const squares = this.state.squares.slice();
if (this.calculateWinner(squares)) {
return;
} else {
squares[i] = this.state.Xturn ? "X" : "Y";
this.setState({
squares: squares,
Xturn: !this.state.Xturn,
});
}
}
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[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
createTable(rows) {
let table = [];
let child_key = 0;
for (let i = 0; i < rows; i++) {
let children = [];
for (let j = 0; j < rows; j++) {
children.push(
<Square
key={child_key}
indicator={child_key}
value={this.state.squares[child_key]}
handleClick={(d) => this.changeTurn(d)}
/>
);
child_key++;
}
table.push(<div className="board-row">{children}</div>);
}
return table;
}
render() {
console.log("render");
const winner = this.calculateWinner(this.state.squares);
if (winner) {
console.log("done");
alert("we have a winner");
}
return <div className="board">{this.createTable(3)}</div>;
}
}
And it's Square component
export class Square extends React.Component {
constructor(props) {
super(props);
}
OnClick = () => {
this.props.handleClick(this.props.indicator);
};
render() {
return (
<div className="square" onClick={this.OnClick}>
{this.props.value}
</div>
);
}
}
When a player put three of them in a row (wins), why I get two alerts (we have a winner) ??
this is what i get in console
render
done
render
done
Why React calls render() two time??
That is the default behaviour. You may refer to https://reactjs.org/docs/react-component.html for the lifecycle methods. Basically, first the constructor is loaded then the render method. To elaborate a bit you can view also this answer - Why is render() being called twice in ReactNative?. In your case first the initial values are called, then the setState takes place. If you dont want to display something just use condition.
My task is to build a tic tac toe game using react. One of the things that I need to implement is the ability to undo previous moves. I'm looking for some help with removing a single element from an array based on the selection. I have an if/else if statement that checks if the selected box has a value of X or O. If it does I need to delete that value from the board.
class GameBoard extends Component {
constructor(props) {
super(props);
this.state = {
box: Array(9).fill(''),
isNext: true
};
}
handleClick(i) {
debugger
const box = this.state.box.slice();
if (box[i].includes('X') || box[i].includes('O')) {
} else if (box[i].includes('')) {
box[i] = this.state.isNext ? 'X' : 'O';
this.setState({ box: box, isNext: !this.state.isNext });
}
}
renderSquare(i) {
return <Selection value={this.state.box[i]} onClick={() => this.handleClick(i)} />
}
render() {
const winner = calculateWinner(this.state.box);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else if (winner && winner === 'Draw') {
status = winner;
}
else {
status = 'Next Player: ' + (this.state.isNext ? 'X' : 'O');
}
return (
<div>
<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>
);
}
}
function calculateWinner(box) {
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 (box[a] && box[a] === box[b] && box[a] === box[c]) {
return box[a];
}
else if (!box.includes('')) {
return 'Draw';
}
}
return null;
}
export default GameBoard;
You could use the index i to update the corresponding item value in array of boxes to achieve this:
handleClick(i) {
debugger
const box = this.state.box.slice();
if (box[i].includes('X') || box[i].includes('O')) {
box[i] = '' // Reset the value of box item at i in box array
this.setState({ box: box, isNext: !this.state.isNext }); // Trigger re-render
} else if (box[i].includes('')) {
box[i] = this.state.isNext ? 'X' : 'O';
this.setState({ box: box, isNext: !this.state.isNext });
}
}
I am new to react.js and this is literaly the code provided in the tutorial on their website to make a tic tac toe game. However, when i try and compile, i get -> 'React' must be in scope when using JSX. I have copied and pasted tutorial code. I am currently using vim editor. If there is a better editor to use please suggest!
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
class Board extends React.Component {
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)} //onclick button
/>
);
}
render() {
return (
<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 = {
history: [
{
squares: Array(9).fill(null)
}
],
stepNumber: 0,
xIsNext: true
};
}
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? "X" : "O";
this.setState({
history: history.concat([
{
squares: squares
}
]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext
});
}
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0
});
}
render() {
const history = this.state.history; //to store history of
//game
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
let status;
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (this.state.xIsNext ? "X" : "O");
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={i => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(<Game />, 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;
}
Since you are using class Game extends React.Component you will need the dependency to the included in the file on the top. Also if ReactDOM.render Call is in the same file, you will have to include that dependency too.
Also I hope you have React and ReactDOM dependencies in package.json file.
Put this at the top of the file.
Import React from ‘react’;
Import ReactDOM from ‘react-dom’;