Closed Makavto closed 11 months ago
Hi @Makavto! Can you please provide code how FEN position's change works in your component, and check which value for start position do you set, when you click on first move. Are you sure that start position value is a valid FEN? Also please tell me which version of react-chessboard
do you use?
I am using the last version, 4.3.2
I am sure that FEN position is always correct, chess.js validates it for me.
I checked the getPositionObject
function and it return correct position object, the problem is somewhere in transform: translate()
:
https://github.com/Clariity/react-chessboard/assets/93901579/21492f71-7336-46dd-85b7-4472c943c63b
Here is my ChessBoard.tsx:
interface IChessBoardProps {
startingFen?: string;
makeMove: (moveCode: string) => void;
boardOrientation: 'black' | 'white',
isMovingBlocked?: boolean
}
type ISquares = {
[key in Square]?: any
}
const ChessBoard = memo(function ChessBoard({startingFen, boardOrientation, makeMove, isMovingBlocked}: IChessBoardProps) {
const [game, setGame] = useState(new Chess(startingFen));
const [moveFrom, setMoveFrom] = useState<Square | null>(null);
const [moveTo, setMoveTo] = useState<Square | null>(null);
const [optionSquares, setOptionSquares] = useState<ISquares>({});
const [dangerSquares, setDangerSquares] = useState<ISquares>({});
const [showPromotionDialog, setShowPromotionDialog] = useState(false);
const checkDangerSquares = () => {
setDangerSquares({});
if (game.isCheck() || game.isCheckmate()) {
const board = game.board();
const turn = game.turn();
let newDangerSquares: ISquares = {};
for (let row of board) {
for (let square of row) {
if (square?.type === 'k' && (square.color === 'b' && turn === 'b' || square.color === 'w' && turn === 'w')) {
newDangerSquares[square.square] = {
boxShadow: 'inset 0 0 5px 5px #B15653'
}
}
}
}
setDangerSquares(newDangerSquares);
}
}
useEffect(() => {
if (!!startingFen) {
game.load(startingFen);
checkDangerSquares();
}
}, [startingFen])
const getMoveOptions = (square: Square) => {
if (game.get(square).color === 'b' && boardOrientation === 'white' || game.get(square).color === 'w' && boardOrientation === 'black') {
return;
}
const moves = game.moves({
verbose: true,
square
});
if (moves.length === 0) {
setOptionSquares({});
return false;
}
const newSquares: ISquares = {};
moves.map((move) => {
newSquares[move.to] = {
background:
game.get(move.to) &&
game.get(move.to).color !== game.get(square).color
? "radial-gradient(circle, rgba(0,0,0,.1) 85%, transparent 85%)"
: "radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)",
borderRadius: "50%",
};
return move;
});
newSquares[square] = {
background: "rgba(255, 255, 0, 0.4)",
};
setOptionSquares(newSquares);
return true;
}
const onSquareClick = (square: Square) => {
if (isMovingBlocked) {
return;
}
if (!moveFrom) {
const hasMoveOptions = getMoveOptions(square);
if (hasMoveOptions) setMoveFrom(square);
return;
}
// to square
if (!moveTo) {
// check if valid move before showing dialog
const moves = game.moves({
verbose: true,
square: moveFrom,
});
const foundMove = moves.find(
(m) => m.from === moveFrom && m.to === square
);
// not a valid move
if (!foundMove) {
// check if clicked on new piece
const hasMoveOptions = getMoveOptions(square);
// if new piece, setMoveFrom, otherwise clear moveFrom
setMoveFrom(hasMoveOptions ? square : null);
return;
}
// valid move
setMoveTo(square);
// if promotion move
if (
(foundMove.color === "w" &&
foundMove.piece === "p" &&
square[1] === "8") ||
(foundMove.color === "b" &&
foundMove.piece === "p" &&
square[1] === "1")
) {
setShowPromotionDialog(true);
return;
}
// is normal move
const move = game.move({
from: moveFrom,
to: square,
promotion: "q",
});
checkDangerSquares();
makeMove(move.san);
// if invalid, setMoveFrom and getMoveOptions
if (move === null) {
const hasMoveOptions = getMoveOptions(square);
if (hasMoveOptions) setMoveFrom(square);
return;
}
setMoveFrom(null);
setMoveTo(null);
setOptionSquares({});
return;
}
}
const onPromotionPieceSelect = (piece?: PromotionPieceOption) => {
// if no piece passed then user has cancelled dialog, don't make move and reset
if (piece && moveFrom && moveTo) {
const move = game.move({
from: moveFrom,
to: moveTo,
promotion: piece[1].toLowerCase() ?? "q",
});
setGame(game);
checkDangerSquares();
makeMove(move.san);
}
setMoveFrom(null);
setMoveTo(null);
setShowPromotionDialog(false);
setOptionSquares({});
return true;
}
return (
<div className={styles.board}>
<Chessboard
animationDuration={200}
arePiecesDraggable={false}
position={game.fen()}
boardOrientation={boardOrientation}
onSquareClick={onSquareClick}
customSquareStyles={{
...optionSquares,
...dangerSquares,
}}
onPromotionPieceSelect={onPromotionPieceSelect}
promotionToSquare={moveTo}
showPromotionDialog={showPromotionDialog}
areArrowsAllowed={true}
customSquare={CustomSquare}
/>
</div>
);
})
const CustomSquare = React.forwardRef(({squareColor, children, style}: CustomSquareProps, ref) => {
return (
<div style={style} className={`${squareColor === 'black' ? styles.darkSquare : styles.lightSquare}`}>
{children}
</div>
)
})
export default ChessBoard
And here is how I use it in GamePage.tsx:
const getPositionAfterMove = (positionBefore: string, moveCode: string) => {
const game = new Chess(positionBefore);
game.move(moveCode);
return game.fen();
}
...some code here
<ChessBoard
startingFen={activeMove ? getPositionAfterMove(activeMove.positionBefore, activeMove.moveCode) : undefined}
isMovingBlocked={activeMove?.number !== chessGame.gameMoves[chessGame.gameMoves.length - 1].moveNumber}
makeMove={onMakeMove}
boardOrientation={chessGame.blackPlayerId === user?.id ? 'black' : 'white'}
/>
I guess the problem is here in useEffect
: The game.load()
method change's Chess objects, but doesn't make your Chessboard
component rerender
useEffect(() => {
if (!!startingFen) {
game.load(startingFen);
checkDangerSquares();
// here will be setGame() call which will force your component rerender `Chessboard`
}
}, [startingFen])
I highly reccomend you to use approach with useMemo
like in this example, where the external FEN changes work fine
https://react-chessboard.vercel.app/?path=/docs/example-chessboard--analysis-board
const MyChessBoard = (props: ChessBoardProps) = {
const game = useMemo(() => new Chess(), []);
const [chessBoardPosition, setChessBoardPosition] = useState(game.fen());
useEffect(() => {
if (!!props.startingFen) {
game.load(startingFen);
checkDangerSquares();
setChessBoardPosition(game.fen)
}
}, [startingFen])
return (
<ChessBoard position={chessBoardPosition}/>
)
I've tried it with setGame()
like this:
useEffect(() => {
if (!!startingFen) {
setGame(new Chess(startingFen));
checkDangerSquares();
}
}, [startingFen])
and it didn't work either :(
I will try useMemo
approach
I've only now noticed,that you use custom squares JSX. Can you please also try to comment customSquare={CustomSquare}
line and check, is the issue still appears? Make sure your custom style squares have right css position props
Yes! You are right, the problem was in custom squares! I will edit styles for them. Thanks a lot for your help!
Hi! Here is an example of issue:
https://github.com/Clariity/react-chessboard/assets/93901579/9f04b2f2-6d34-4ef5-8078-b37636946ddd
If I change FEN to a couple of moves it's ok, but if I change it to the start of the game it messes things up :(
Is there any way to fix it?