Rohit kumar
Rohit kumar

Reputation: 23

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?

Upvotes: 0

Views: 62

Answers (1)

Tushar Shahi
Tushar Shahi

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

Related Questions