React JS print the choice of the user - reactjs

I'm trying React.js and I followed a tutorial to make a morpion game. Now, I'm trying to get the choice of the user, where he clicked, and print the column and line. Here's what I've done so far:
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 = {
history: [{
squares: Array(9).fill(null),
choice: null,
}],
stepNumber: 0,
xIsNext: true,
};
}
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();
//Si quelqu'un a gagné, on empêche le clic
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,
});
}
render() {
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
const choice = (current) => { this.calculateChoice(current.squares) };
const moves = history.map((step, move) => {
const desc = move ?
'Revenir au tour n°' + move :
'Revenir au début de la partie';
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
let status;
if (winner){
status = winner + ' a gagné';
} else {
status = 'Prochain joueur : ' + (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>Coup {choice}</div>
<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;
}
function calculateChoice(squares){
const lines = [
[0,0],[0,1],[0,2],
[1,0],[1,1],[1,2],
[2,0],[2,1],[2,2],
];
for (let i = 0; i < lines.length; i++){
const [a, b] = lines[i];
return "test ".squares[a];
}
return "test";
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
I defined a const choice, where I want to put the choice of the user. Then I call calculateChoice, but nothing is displayed when I try { choice }. I don't know why. Also, I don't know how to get the line and column of where the user clicked. I made a tab of the different choices but then I don't know how to compare them to the Square the user clicked.
Thanks
Edit:
So I changed the const choice:
const choice = calculateChoice(current.squares);
Now it works, I get a display. But I don't know how to get the column and line of where the user clicked. I started with the calculateChoice but I don't know where I'm going with this.
What I would like is:
The user click on a Square. The 'X' or 'O' is affected to the Square and then it displays "choice" which corresponds to (line,column) of where the user clicked. Thanks for any explanation to how to develop that!

This is a function, const choice = (current) => { this.calculateChoice(current.squares) };
If you render <div>Coup {choice}</div>, of course it won't render any value, because it's a function.
This function won't return proper value because it's not meant to return anything. You can either do const choice = (current) => { return this.calculateChoice(current.squares) }; or const choice = (current) => this.calculateChoice(current.squares);
If you want to call this.calculateChoice(current.squares), make sure it's inside the class component.

Related

create linked list in React - Expanding on the React Tic-Tac-Toe Tutorial

I'm trying to expand on the official react Tic-Tac-Toe tutorial: https://reactjs.org/tutorial/tutorial.html#completing-the-game by creating a linked list to search for the win condition. However, I am having issues accessing the information. Does anyone know where I'm going wrong? I keep getting undefined with my console.log on line 138
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
function Square(props) {
return (
\<button className="square" onClick={props.onClick}\>
{props.value}
{props.rightmiddle}
{props.righttop}
{props.rightbottom}
{props.lefttop}
{props.leftbottom}
{props.top}
{props.bottom}
\</button\>
);
}
class Board extends React.Component {
renderSquare(i) {
return (
\<Square
value={this.props.squares\[i\]}
rightmiddle = {null}
righttop = {null}
rightbottom = {null}
leftmiddle = {null}
lefttop = {null}
leftbottom = {null}
top = {null}
bottom = {null}
onClick={() =\>
this.props.onClick(i)
}
/\>
);
}
forloop(x){
const numcolumns = 3;
const options = [];
for (let i = 0; i < numcolumns; i++) {
options.push(this.renderSquare(i + x));
}
return (
<div className="board-row">
{options}
</div>
)
}
render() {
const numrows = 3;
const linklistTRow = [];
const linklistBRow = [];
const linklistMRow = [];
const rows = [];
for(let i = 0; i < numrows; i++)
{
rows.push(this.forloop(i*numrows));
if (i === 0) { linklistTRow.push(rows[0])};
if (i === 1) { linklistMRow.push(rows[1])};
if (i === 2) { linklistBRow.push(rows[2])};
};
return (
<div> {rows} </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;
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)}
log = {console.log(this.props.value)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}
// ========================================
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(\<Game /\>);
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;
}
I have been trying to use props.value to call the square number (0 to 8) however, this is showing undefined results.

How can I prevent re-rendering of functional child element

While creating a tic-tac-toe game on React js. whenever I click on a single tile, it re-renders for all other tiles too.
const Game = () => {
const [xIsNext, setXIsNext] = useState(true);
const [stepNumber, setStepNumber] = useState(0);
const [history, setHistory] = useState([{ squares: Array(9).fill(null) }]);
const updatedHistory = history;
const current = updatedHistory[stepNumber];
const winner = CalculateWinner(current.squares);
const move = updatedHistory.map((step, move) => {
const desc = move ? `Go to # ${move}` : "Game Start";
const jumpTo = (step) => {
setStepNumber(step);
setXIsNext(step % 2 === 0);
};
return (
<div key={move}>
<button className="btninfo" onClick={() => jumpTo(move)}>{desc}</button>
</div>
);
});
let status;
if (winner) {
status = `Winner is ${winner}`;
} else {
status = `Turn for Player ${xIsNext ? "X" : "O"}`;
}
const handleClick = (i) => {
const latestHistory = history.slice(0, stepNumber + 1);
const current = latestHistory[latestHistory.length - 1];
const squares = current.squares.slice();
const winner = CalculateWinner(squares);
if (winner || squares[i]) {
return;
}
squares[i] = xIsNext ? "X" : "O";
setHistory(history.concat({ squares: squares }));
setXIsNext(!xIsNext);
setStepNumber(history.length);
};
const handleRestart = () => {
setXIsNext(true);
setStepNumber(0);
setHistory([{ squares: Array(9).fill(null) }]);
};
return (
<div className="game">
<div className="game-board">
<div className="game-status">{status}</div>
<Board onClick={handleClick} square={current.squares} />
</div>
<div className="game-info">
<button className="btninfo" onClick={handleRestart}>Restart</button>
<div>{move}</div>
</div>
</div>
);
};
export default Game;
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[b] === squares[c]) {
return squares[a];
}
}
return null;
};
and the Board component on which Parameter is passed is
const Board = (props) => {
const renderSquare = (i) => {
return <Square value={props.square[i]} onClick={() => props.onClick(i)} />;
};
return (
<div>
<div className="border-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="border-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="border-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
);
};
export default Board;
which leads to the single Square component mentioned below, which re-renders for all tiles if we click a single tile.
const Square = (props) => {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
};
export default Square;
I tried with useCallback on handleClick function, and kept the second parameter as an empty array, then also it didn't work.
How can I prevent the re-rendering of other tiles?
If a React component's parent rerenders, it will cause the child to rerender unless the component is optimized using React.memo or the shouldComponentUpdate life cycle method handles this.
Since yours is a functional component just do this while exporting:
export default React.memo(Square);
As mentioned in the docs, React.memo does a shallow check of props and if found different returns true. This can be controlled using the second argument of the function, which is a custom equality checker.
One of the props for Square is an object (the function - onClick={() => props.onClick(i)}). This is obviously created new everytime. A function object is equal only to itself.
You will have to use useCallback so the function is not created in every cycle.
const handleClick = useCallback((i) => {
const latestHistory = history.slice(0, stepNumber + 1);
const current = latestHistory[latestHistory.length - 1];
const squares = current.squares.slice();
const winner = CalculateWinner(squares);
if (winner || squares[i]) {
return;
},[history,latestHistory,squares]);
````
You might have to do the same here too:
````
const renderSquare = (i) => {
cont clickHandler = useCallback(() => props.onClick(i),[props.onClick]);
return <Square value={props.square[i]} onClick={clickHandler} />;
};
````

useState not setting value

I am trying to make a calculator, but when i click the number buttons, the screen doesnt update. I checked the handler, and it seems like it should work. I also logged the calc(see code for reference) and it is not updating. What is wrong?
import React, {useState} from 'react';
import Screen from './screen.js'
import Button from './button.js'
import ButtonBox from './buttonBox.js'
const btnValues = [
["C", "+-", "%", "/"],
[7, 8, 9, "X"],
[4, 5, 6, "-"],
[1, 2, 3, "+"],
[0, ".", "="],
];
const CalcBody =() => {
{/*res as in result */}
let [calc, setCalc] = useState({
sign: "",
num: 0,
res: 0,
});
const numClickHandler = (e) => {
e.preventDefault();
const value = e.target.innerHTML;
if (calc.num.length < 16) {
setCalc({
...calc,
num:
calc.num === 0 && value === "0"
? "0"
: calc.num % 1 === 0
? Number(calc.num + value)
: calc.num + value,
res: !calc.sign ? 0 : calc.res,
});
}
console.log(calc.num)
};
return (
<div className="wrapper">
<Screen value={calc.num ? calc.num : calc.res} />
<ButtonBox>
{btnValues.flat().map((btn, i) => {
return (
<Button
key={i}
className={btn === "=" ? "equals" : ""}
value={btn}
onClick={numClickHandler
}
/>
);
})}
</ButtonBox>
</div>
);
}
export default CalcBody;

How to prevent re-rendering with callbacks as props in ReactJS?

I'm practicing with the new hooks functionnality in ReactJS by refactoring the code from this tutorial with TypeScript.
I am passing a callback from a parent component to a child component threw props that has to be executed on a click button event.
My problem is this: I have an alert dialog that appears twice instead of once when the game has been won.
I assumed this was caused by a component re-rendering so I used the useCallback hook to memoize the handleClickOnSquare callback. The thing is, the alert dialog still appears twice.
I guess I'm missing something that has a relation with re-rendering, does someone have an idea what might cause this behavior ?
Here is my code:
Game.tsx
import React, { useState, useCallback } from 'react';
import './Game.css';
interface SquareProps {
onClickOnSquare: HandleClickOnSquare
value: string;
index: number;
}
const Square: React.FC<SquareProps> = (props) => {
return (
<button
className="square"
onClick={() => props.onClickOnSquare(props.index)}
>
{props.value}
</button>
);
};
interface BoardProps {
squares: Array<string>;
onClickOnSquare: HandleClickOnSquare
}
const Board: React.FC<BoardProps> = (props) => {
function renderSquare(i: number) {
return (
<Square
value={props.squares[i]}
onClickOnSquare={props.onClickOnSquare}
index={i}
/>);
}
return (
<div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="board-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="board-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
);
};
export const Game: React.FC = () => {
const [history, setHistory] = useState<GameHistory>(
[
{
squares: Array(9).fill(null),
}
]
);
const [stepNumber, setStepNumber] = useState(0);
const [xIsNext, setXIsNext] = useState(true);
const handleClickOnSquare = useCallback((index: number) => {
const tmpHistory = history.slice(0, stepNumber + 1);
const current = tmpHistory[tmpHistory.length - 1];
const squares = current.squares.slice();
// Ignore click if has won or square is already filled
if (calculateWinner(squares) || squares[index]) return;
squares[index] = xIsNext ? 'X' : 'O';
setHistory(tmpHistory.concat(
[{
squares: squares,
}]
));
setStepNumber(tmpHistory.length);
setXIsNext(!xIsNext);
}, [history, stepNumber, xIsNext]);
const jumpTo = useCallback((step: number) => {
setHistory(
history.slice(0, step + 1)
);
setStepNumber(step);
setXIsNext((step % 2) === 0);
}, [history]);
const current = history[stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ?
'Go back to move n°' + move :
'Go back to the start of the party';
return (
<li key={move}>
<button onClick={() => jumpTo(move)}>{desc}</button>
</li>
);
});
let status: string;
if (winner) {
status = winner + ' won !';
alert(status);
} else {
status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClickOnSquare={handleClickOnSquare}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
function calculateWinner(squares: Array<string>): string | null {
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;
}
types.d.ts
type GameHistory = Array<{
squares: Array<string>
}>;
type HandleClickOnSquare = (index: number) => void;
Thanks
Your code is too long to find the cause of extra rerender. Note that React might need an extra render.
To avoid an extra alert use useEffect:
let status: string;
if (winner) {
status = winner + ' won !';
} else {
status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}
useEffect(() => {
if (winner) alert(status)
}, [winner]);

reactjs- state of random numbers does not match what is output on screen

I am trying to make my first react.js app using the tutorial on the react website as a guide. I have a grid of 25 buttons that render different multiplication problems when click on (they start out blank). Once clicked on, the numbers appear and there is a textbox with an onChange attribute that checks the value of the input against the random numbers (the random numbers are created and stored in the Boards state.)
I am trying to log correct or incorrect to the console right now for testing purposes, but no matter what is put in , i always get incorrect, and the numbers appearing on the screen for the multiplication problem do no match console.log(this.state.num1) and console.log(this.state.num2).
I can not seem to figure out why there is a difference. Also, any advice on how I should restructure my components in a more react-like way would be greatly appreciated. Thank you.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
/*stop clicking ability after clicking a problem (prevent default) and enable it when submit is done.*/
/* let btnStyle = {
backgroundColor: 'green'
} */
/*we also changed onClick={() => props.onClick()} to just onClick={props.onClick},
as passing the function down is enough for our example. Note that onClick={props.onClick()}
would not work because it would call props.onClick immediately instead of passing it down.*/
class Square extends React.Component
{
render()
{
return (
<button className="square" onClick = {this.props.onClick} style = {{backgroundColor: this.props.backgroundColor}}>
{this.props.value}
{this.props.txt}
</button>
);
}
}
class Board extends React.Component
{
constructor(props)
{
super(props);
this.state =
{
squares: Array(25).fill(null),
xIsNext: true,
squaresColor: Array(25).fill('null'),
num1: generateRandomNumber(),
num2: generateRandomNumber(),
ans: function(a,b){return(a * b);},
sqTxt: Array(25).fill(null)
};
//this.handleAnswer = this.handleAnswer.bind(this);
}
handleClick(i)
{
const squares = this.state.squares.slice(); // makes a mutable copy of the array
const squaresColor = this.state.squaresColor.slice(); // makes a mutable copy of the array
const sqTxt = this.state.sqTxt.slice(); // makes a mutable copy of the array
if (/* calculateWinner(squares) || */ squares[i])
{
return;
}
squaresColor[i] = 'blue';
sqTxt[i] = <input onChange = {(e,i) => this.handleAnswer(e, i)} className = 'answer-input' type = 'text' name = 'answer' />;
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
squaresColor: squaresColor,
num1: generateRandomNumber(),
num2: generateRandomNumber(),
sqTxt: sqTxt
});
squares[i] = this.state.num1 + ' X ' + this.state.num2;
}
/*When an event is invoked, it will be passed an event object as it's first argument. You can name evt whatever you like. Common names are e evt and event.*/
handleAnswer(e, i)
{
const userAnswer = e.target.value;
const num1 = this.state.num1;
const num2 = this.state.num2;
const correctAnswer = num1 * num2;
console.log('num1: ' + num1);
console.log('num2: ' + num2);
console.log('userAnswer: ' + userAnswer);
console.log('correctAnswer: ' + correctAnswer)
if(userAnswer === correctAnswer)
{
console.log('correct');
}
else
{
console.log('incorrect');
}
}
renderSquare(i)
{
return (
<Square
value={this.state.squares[i]}
onClick = {() => this.handleClick(i)}
backgroundColor = {this.state.squaresColor[i]}
txt = {this.state.sqTxt[i]}
/>
);
}
render()
{
return (
<div className = 'all-rows'>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
{this.renderSquare(3)}
{this.renderSquare(4)}
</div>
<div className="board-row">
{this.renderSquare(5)}
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
{this.renderSquare(9)}
</div>
<div className="board-row">
{this.renderSquare(10)}
{this.renderSquare(11)}
{this.renderSquare(12)}
{this.renderSquare(13)}
{this.renderSquare(14)}
</div>
<div className="board-row">
{this.renderSquare(15)}
{this.renderSquare(16)}
{this.renderSquare(17)}
{this.renderSquare(18)}
{this.renderSquare(19)}
</div>
<div className="board-row">
{this.renderSquare(20)}
{this.renderSquare(21)}
{this.renderSquare(22)}
{this.renderSquare(23)}
{this.renderSquare(24)}
</div>
</div>
);
}
}
class Game extends React.Component
{
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
</div>
</div>
);
}
}
ReactDOM.render(
<Game />,
document.getElementById('root')
);
function generateRandomNumber()
{
return Math.floor(Math.random() * 10);
}
I believe your user input is a string, so you are trying to compare a string to an integer.
I suggest you use parseInt() or Number() functions to format the userAnswer into a valid type to compare.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt
handleAnswer(e, i)
{
const userAnswer = parseInt(e.target.value, 10);
const num1 = this.state.num1;
const num2 = this.state.num2;
const correctAnswer = num1 * num2;
console.log('num1: ' + num1);
console.log('num2: ' + num2);
console.log('userAnswer: ' + userAnswer);
console.log('correctAnswer: ' + correctAnswer)
if(userAnswer === correctAnswer)
{
console.log('correct');
}
else
{
console.log('incorrect');
}
}
Good Luck!

Resources