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.
Related
I'm attempting to build a Minesweeper game as practice using React. I have a main Minesweeper.js file with all of the functionality.
export class Tile {
constructor(board, pos) {
this.board = board;
this.pos = pos;
this.bombed = false;
this.explored = false;
this.flagged = false;
}
adjacentBombCount() {
let bombCount = 0;
this.neighbors().forEach(neighbor => {
if (neighbor.bombed) {
bombCount++;
}
});
return bombCount;
}
explore() {
if (this.flagged || this.explored) {
return this;
}
this.explored = true;
if (!this.bombed && this.adjacentBombCount() === 0) {
this.neighbors().forEach(tile => {
tile.explore();
});
}
}
neighbors() {
const adjacentCoords = [];
Tile.DELTAS.forEach(delta => {
const newPos = [delta[0] + this.pos[0], delta[1] + this.pos[1]];
if (this.board.onBoard(newPos)) {
adjacentCoords.push(newPos);
}
});
return adjacentCoords.map(coord => this.board.grid[coord[0]][coord[1]]);
}
plantBomb() {
this.bombed = true;
}
toggleFlag() {
if (!this.explored) {
this.flagged = !this.flagged;
return true;
}
return false;
}
}
Tile.DELTAS = [[-1, -1], [-1, 0], [-1, 1], [ 0, -1],
[ 0, 1], [ 1, -1], [ 1, 0], [ 1, 1]];
export class Board {
constructor(gridSize, numBombs) {
this.gridSize = gridSize;
this.grid = [];
this.numBombs = numBombs;
this.generateBoard();
this.plantBombs();
}
generateBoard() {
for (let i = 0; i < this.gridSize; i++) {
this.grid.push([]);
for (let j = 0; j < this.gridSize; j++) {
const tile = new Tile(this, [i, j]);
this.grid[i].push(tile);
}
}
}
onBoard(pos) {
return (
pos[0] >= 0 && pos[0] < this.gridSize &&
pos[1] >= 0 && pos[1] < this.gridSize
);
}
plantBombs() {
let totalPlantedBombs = 0;
while (totalPlantedBombs < this.numBombs) {
const row = Math.floor(Math.random() * (this.gridSize - 1));
const col = Math.floor(Math.random() * (this.gridSize - 1));
let tile = this.grid[row][col];
if (!tile.bombed) {
tile.plantBomb();
totalPlantedBombs++;
}
}
}
lost() {
let lost = false;
this.grid.forEach(row => {
row.forEach(tile => {
if (tile.bombed && tile.explored) {
lost = true;
}
});
});
return lost;
}
won() {
let won = true;
this.grid.forEach(row => {
row.forEach(tile => {
if (tile.flagged === tile.revealed || tile.flagged !== tile.bombed) {
won = false;
}
});
});
return won;
}
}
Then I have three React components: Board.jsx
import React from "react";
import Tile from "./tile";
class Board extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
{this.props.board.grid.map((row, idx) => {
return (<div key={idx}>
{row.map((tile, idx) => {
return (
<div key={idx}>
<Tile
tile={tile}
updateGame={this.props.updateGame}/>
</div>
)
})}
</div>)
})}
</div>
)
}
}
export default Board;
Tile.jsx
import React from "react";
class Tile extends React.Component {
constructor(props) {
super(props)
this.checkState = this.checkState.bind(this)
}
checkState() {
console.log(this)
if (this.props.tile.bombed) {
return "💣"
} else if (this.props.tile.explored) {
return (this.props.tile.adjacentBombCount())
} else if (this.props.tile.flagged) {
return "⚐"
} else {
return ""
}
}
render() {
return(
<div>{this.checkState()}</div>
)
}
}
export default Tile;
and finally a Game.jsx
import React from "react";
import * as Minesweeper from "../minesweeper"
import Board from "./board";
class Game extends React.Component {
constructor(props) {
super(props);
let board = new Minesweeper.Board(10, 5)
this.state = {
board: board
}
this.updateGame = this.updateGame.bind(this);
}
updateGame() {
}
render() {
return(
<Board
board={this.state.board}
updateGame={this.updateGame}/>
)
}
}
export default Game
Within Tile.jsx, I want the render to display adjacent bomb counts using the function from the main Tile js class (in minesweeper.js). This function is accessible when I first render my Tiles as React Components; however, if I change something (by going into components and updating), the function is no longer accessible. To help explain, here is the console.log of this.props.tile on the first render:
However, after re-rendering from updating a component, this is what this.props.tile looks like .
Therefore, it will cause an error when I try to add the bomb count, as it cannot find the function. Can someone explain why the function is disappearing and how I can access it when Components change? Thanks!
This was an issue with the React DevTools specifically, and it will be addressed in an update, see: https://github.com/facebook/react/issues/24781
I have a small Tic tac toe game, everything is fine except I am struggling on finding where to put finish function in the code. Let me show you;
import React, { useState } from "react";
import { isGameOver } from "../Helper/helper";
import Square from "./Square";
const Board: React.FC = () => {
const [squares, setSquares] = useState(new Array(9).fill(""));
const [activePlayer, setActivePlayer] = useState("Player 1");
const [winner, setWinner] = useState<any>();
const clickSquare = (id: number): void => {
if (!squares[id]) {
if (activePlayer === "Player 1") {
setActivePlayer("Player 2");
} else {
setActivePlayer("Player 1");
}
}
setSquares((prevSquares) => {
return prevSquares.map((square, i) => {
if (i === id && !square) {
square = activePlayer === "Player 1" ? "X" : "O";
}
return square;
});
});
};
return (
<div className="board">
<div className="game-field">
{squares.map((square, i) => {
return (
<Square clickSquare={clickSquare} square={square} key={i} i={i} />
);
})}
</div>
<p
style={{
marginRight: activePlayer === "Player 1" ? "250px" : "-250px",
}}
>
{activePlayer + "'s turn"}
</p>
<p> </p>
</div>
);
};
export default Board;
This is basically the whole game and I have a finishing function in another file like this;
export const isGameOver = (squares: String[]) => {
const won = [
[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 < won.length; i++) {
const [x, y, z] = won[i];
if (squares[x] && squares[x] === squares[y] && squares[y] === squares[z]) {
return squares[x];
}
}
return null;
};
The finishing function also working fine, but since useState is working asynchoronously and I know that, I'm struggling where to put it. If I put inside of the click function it is not able to react new state, only the state just before. If I put outside with just If statement, it says too many renders cause I'm setting the winner etc.. I mean I want to also stop the game, stop clicking events etc.. Couldn't find a way how to implement this.
Seems like you need useEffect. Basically, check if the game is over, everytime a square is clicked:
useEffect(() => {
if(squares){
if(isGameOver(squares)){
setSquares(null)
/* Do other stuff */
}
}
}, [squares])
By passing squares to the dependency array, you tell react that you want this useEffect to be run everytime squares state variable changes. You can read more here
I'm creating a quick tic-tac-toe implementation in React, and ran into an issue, seen here:
Here is the code:
https://codesandbox.io/s/cold-grass-yr6mn?file=/src/container/Board.jsx
The Problems:
1.) It prints the wrong winner (the game changes the turn after the user wins)
2.) When a user has won, I want to display "Winner: X", instead of "Next: X".
Why it's failing:
In my Board.jsx, I pass the following method down to my Square.jsx component, which updates the board state, and the player turn:
const updateBoard = (squareIndex) => {
setGameState((prevBoard) => {
// handle previously set value
if (prevBoard[squareIndex] !== "") {
return prevBoard;
}
// otherwise update the board
let updatedBoard = [...prevBoard];
updatedBoard[squareIndex] = playerTurn;
return updatedBoard;
});
// update player turn
setPlayerTurn((turn) => (turn === "X" ? "O" : "X"));
};
In the same file I have a useEffect that checks when the gameState or player turn update. This method determines the winner.
// board update events/actions
useEffect(() => {
const isWinner = () => {
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 (
gameState[a] &&
gameState[a] === gameState[b] &&
gameState[a] === gameState[c]
) {
return true;
}
}
return false;
};
if (isWinner()) {
alert(`${playerTurn} is the winner.`);
return;
}
}, [gameState, playerTurn]);
So the reason why it is printing the wrong winner is because once someone wins, it will still update the player turn, and then will display the losing player.
My Question
What is the best way to handle updating player turn? If I set the player in my useEffect, I will get an infinite render loop. I can think of some hacky ways to fix it like printing the opposite of whatever the current player turn is as the winner. But that does not seem ideal.
Any advice on the best way to approach this fix is appreciated.
Thanks!
I have tried to simplify the logic a little bit.
Link: https://codesandbox.io/s/elastic-moon-bcps0?file=/src/container/Board.jsx:0-1829
JS:
import React, { useEffect, useState } from "react";
import Square from "../components/Square/Square";
import styles from "./Board.module.css";
const Board = () => {
const [gameState, setGameState] = useState([]);
const [playerTurn, setPlayerTurn] = useState("X");
// onMount / todo: when user resets board
useEffect(() => {
const initializeGame = () => setGameState(new Array(9).fill(""));
initializeGame();
}, []);
// board update events/actions
useEffect(() => {
let winner = "";
const isWinner = () => {
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 (
gameState[a] &&
gameState[a] === gameState[b] &&
gameState[a] === gameState[c]
) {
winner = gameState[a];
return true;
}
}
return false;
};
if (isWinner()) {
alert(`${winner} is the winner.`);
}
}, [gameState]);
const updateBoard = (squareIndex) => {
if (gameState[squareIndex] !== "") {
return;
}
let updatedBoard = [...gameState];
updatedBoard[squareIndex] = playerTurn;
setGameState(updatedBoard);
// update player turn
setPlayerTurn((turn) => (turn === "X" ? "O" : "X"));
};
return (
<>
<main className={styles.board}>
{gameState.map((cell, position) => (
<Square
boardPosition={position}
displayValue={cell}
updateBoard={updateBoard}
key={position}
/>
))}
</main>
<div>
<p>Next: {playerTurn}</p>
</div>
</>
);
};
export default Board;
You can try this:
Add a state variable to maintain state. Initialize it as false
const [gameFinished, setFinished] = useState(false);
Add a prop to Square as disabled.
Call callback on click based on this:
const clickHandler = () => {
if (!isDisabled) updateBoard(boardPosition);
};
Update className based on this
if (isDisabled) {
classes.push("disabled");
}
Use newly create state variable to pass prop
isDisabled={gameFinished}
Sandbox link
You could update the player turn in your useEffect keeping the gameState as the dependency.
If the isWinner function returns true, then use another type of value to setPlayerTurn to declare winner rather than using another state.
const WINNING_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]
];
function isWinner(gameState) {
for (let i = 0; i < WINNING_LINES.length; i++) {
const [a, b, c] = WINNING_LINES[i];
if (
gameState[a] &&
gameState[a] === gameState[b] &&
gameState[a] === gameState[c]
) {
return true;
}
}
return false;
};
const Board = () => {
const [gameState, setGameState] = useState(new Array(9).fill(""));
const [playerTurn, setPlayerTurn] = useState("X");
const updateBoard = (squareIndex) => {
setGameState((prevBoard) => {
// handle previously set value
if (prevBoard[squareIndex] !== "") {
return prevBoard;
}
// otherwise update the board
let updatedBoard = [...prevBoard];
updatedBoard[squareIndex] = playerTurn;
return updatedBoard;
});
};
React.useEffect(() => {
if (isWinner(gameState)) {
setPlayerTurn(null);
} else {
setPlayerTurn((turn) => (turn === "X" ? "O" : "X"));
}
}, [gameState]);
return (
<>
<main className={styles.board}>
{gameState.map((cell, position) => (
<Square
boardPosition={position}
displayValue={cell}
updateBoard={updateBoard}
key={position}
/>
))}
</main>
<div>
<p>Next: {playerTurn}</p>
</div>
</>
);
};
export default Board;
First let me say, thank you for any help/advice I may receive. I am truly grateful.
I've built a tic-tac-toe app in react (mostly from youtube). It currently only works as a local multiplayer game. I would like to add an option for the player to choose between local multiplayer, or against AI. The ai doesn't need to be advanced, simply picking a random available space will do fine.
My Game.js file looks like this:
import React, { Component } from 'react'
import Board from './Board';
export default class Game extends Component {
constructor(props){
super(props);
this.state= {
xIsNext: true,
stepNumber: 0,
history: [
{ squares: Array(9).fill(null) }
]
}
}
jumpTo(step){
this.setState({
stepNumber: step,
xIsNext: (step%2)===0
})
}
handleClick(i) {
const history = this.state.history.slice(0,this.state.stepNumber+1);
const current = history[history.length-1];
const squares = current.squares.slice();
const winner = calculateWinner(squares);
//stops player from picking a chosen square
if(winner || squares[i]){
return;
}
squares[i] = this.state.xIsNext?'X':'0';
this.setState({
history: history.concat({
squares: squares
}),
xIsNext: !this.state.xIsNext,
stepNumber: history.length
});
}
render() {
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner= calculateWinner(current.squares);
const moves= history.map((step, move)=>{
const desc = move? 'Go to #' + move:'Choose a square to begin the game';
return (
<li key={move}>
<button onClick={()=>{this.jumpTo(move)}}>
{desc}
</button>
</li>
)
});
let status;
if(winner){
status = 'Winner is ' + winner
} else{
status = 'Next Player is ' + (this.state.xIsNext?'X':'0');
}
return (
<div className="game">
<div className="game-board">
<Board onClick={(i)=>this.handleClick(i)}
squares={current.squares} />
</div>
<div>{status}</div>
<ul>{moves}</ul>
</div>
)
}
}
//sets win condition
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[b] === squares[c]){
return squares [a];
}
}
return null;
}
Add an optional param to handleClick(), let's call it computerIsGoing. At the end of handleClick(), add this code:
if ( ! computerIsGoing ) {
this.computersTurn()
}
The computersTurn() function would look something like this (pseudocode):
function computerIsGoing() {
// Look at all the squares, find all the ones not yet filled in, pick a random one (e.g. 5)
let n = 5; // Assume
this.handleClick( n, true );
}
This will cause the computersTurn() function to run after every time the user clicks a square.
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 });
}
}