Reputation: 23
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
onhandleClick
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?
Upvotes: 0
Views: 62
Reputation: 20461
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} />;
};
````
Upvotes: 1