So I started learning React and after doing some basics (counters, todo lists) I decided to do something more challenging; a chessboard with pieces that can be moved around by clicking. My main component is Chessboard. It then renders Tiles that may contain a Piece. Information about the game is stored in Chessboard's state, looking like this:
interface State {
board: Record<string, pieceInterface>;
isPieceMoving: boolean;
movingPieceId: string;
}
I handle moving pieces in Chessboard's method onTileClick(id: string). It is passed to a Tile as a prop and when a tile is clicked it's called with a tile id (like "a4", "f6", "h3"). It has following logic. Game can be in two states: I can be currently moving a piece or I can currently do nothing and then start moving a piece by clicking on it. When I start moving a piece I store it's ID (or rather an ID of a tile on witch piece stands) in state.movingPieceId. When I try to place it I check if the tile is empty and then change state.board accordingly. Here is my code:
onTileClick = (id: string): void => {
if (!this.state.isPieceMoving) {
if (this.state.board[id]) {
this.setState({
isPieceMoving: true,
movingPieceId: id,
});
}
} else {
if (!this.state.board[id]) {
this.setState((state) => {
let board: Record<string, pieceInterface> = state.board;
const piece: pieceInterface = board[state.movingPieceId];
delete board[state.movingPieceId];
board[id] = piece;
// console.log(board[id]);
// console.lob(board);
const isPieceMoving: boolean = false;
const movingPieceId: string = "";
return { board, isPieceMoving, movingPieceId };
});
}
}
};
The first part works just fine. But in the second there is some bug, that I cannot find. When I uncomment this console logs the output looks like this (I want to move a piece from "a2" to "a3"):
Object { "My piece representation" }
Object {
a1: { "My piece representation" },
a3: undefined,
a7: { "My piece representation" },
...
}
My code properly "takes" clicked piece from "a2" (board has no "a2" property) but it apparently does not place it back. Even when printing board[id] gives correct object! I was moving logging line console.log(board) up to see where bug happens. And even when I put it directly behind else it still was saying that "a3" was undefined.
I spend hours trying to understand what was happening. I tried to emulate this fragment of code in console, but there it always worked! Can anybody explain to me what am I doing wrong? Is this some weird React's setState mechanism that I haven't learned yet? Or is it some basic javascript thing, that I am not aware of? Any help would we wonderful, because I cannot move on and do anything else while being stuck here.
EDIT #1
Ok. So I was able to fix this bug by deep copying state.board into board (inspiredy by @Linda Paiste's comment). I just used simple JSON stringify/parse hack:
let board: Record<string, pieceInterface> = JSON.parse(JSON.stringify(state.board));
This fixes the bug but I have no idea why. I will keep this question open, so maybe someone will explain to me what and why was wrong with my previous code.
EDIT #2
Ok. Thanks to @Linda's explanation and reading the docs i finally understood what I was doing wrong. Linda made detailed explanation in an answer below (the accepted one). Thank you all very much!