React Guide - RumenDamyanov/js-chess GitHub Wiki
Learn how to build chess applications using React with the JS Chess project, leveraging modern React patterns including hooks, context, and component composition.
The React implementation demonstrates how to use the popular component-based library to build interactive chess applications. It showcases modern React patterns including hooks, context API, and functional components with TypeScript support.
react/
├── package.json # Dependencies and scripts
├── vite.config.js # Vite build configuration
├── index.html # HTML entry point
├── src/
│ ├── main.jsx # Application entry point
│ ├── App.jsx # Root component
│ ├── components/ # React components
│ │ ├── ChessBoard.jsx # Chess board component
│ │ ├── GameControls.jsx # Game control buttons
│ │ ├── GameStatus.jsx # Status display
│ │ ├── MoveHistory.jsx # Move history component
│ │ ├── SettingsModal.jsx # Settings dialog
│ │ └── LoadingSpinner.jsx # Loading component
│ ├── hooks/ # Custom React hooks
│ │ ├── useChessGame.js # Game logic hook
│ │ ├── useWebSocket.js # WebSocket hook
│ │ ├── useSettings.js # Settings hook
│ │ └── useLocalStorage.js # Storage hook
│ ├── context/ # React context providers
│ │ ├── GameContext.jsx # Game state context
│ │ └── SettingsContext.jsx # Settings context
│ ├── services/ # API services
│ │ └── chessApi.js # API client
│ ├── utils/ # Utility functions
│ │ ├── chessHelpers.js # Chess logic helpers
│ │ └── constants.js # Application constants
│ ├── assets/ # Static assets
│ └── styles/ # Component styles
│ ├── index.css # Global styles
│ └── components/ # Component-specific styles
└── public/ # Public assets
└── pieces/ # Chess piece images
# Clone the repository
git clone --recursive https://github.com/RumenDamyanov/js-chess.git
cd js-chess
# Start the React application
make run-react
# Or manually
cd react
npm install
npm run dev
Visit http://localhost:3004
to see the React chess application in action.
The main.jsx
file bootstraps the React application:
// main.jsx - React application entry point
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import { GameProvider } from './context/GameContext'
import { SettingsProvider } from './context/SettingsContext'
import './styles/index.css'
// Error boundary for development
import ErrorBoundary from './components/ErrorBoundary'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<ErrorBoundary>
<SettingsProvider>
<GameProvider>
<App />
</GameProvider>
</SettingsProvider>
</ErrorBoundary>
</React.StrictMode>
)
The App.jsx
component serves as the application shell:
// App.jsx - Root application component
import React, { useEffect, useState } from 'react'
import { useGame } from './context/GameContext'
import { useSettings } from './context/SettingsContext'
import ChessBoard from './components/ChessBoard'
import GameControls from './components/GameControls'
import GameStatus from './components/GameStatus'
import MoveHistory from './components/MoveHistory'
import SettingsModal from './components/SettingsModal'
import LoadingSpinner from './components/LoadingSpinner'
import ErrorMessage from './components/ErrorMessage'
function App() {
const [showSettings, setShowSettings] = useState(false)
const [error, setError] = useState(null)
const {
gameState,
selectedSquare,
validMoves,
moveHistory,
isPlayerTurn,
isLoading,
createNewGame,
makeMove,
undoLastMove,
getHint,
jumpToMove,
selectSquare,
clearSelection
} = useGame()
const { settings, updateSettings } = useSettings()
useEffect(() => {
// Initialize game on component mount
const initializeGame = async () => {
try {
await createNewGame()
} catch (err) {
setError('Failed to initialize game')
console.error('Initialization error:', err)
}
}
initializeGame()
}, [createNewGame])
const handleSquareClick = async (position) => {
if (!isPlayerTurn || isLoading) return
try {
if (selectedSquare === position) {
clearSelection()
return
}
if (selectedSquare && validMoves.includes(position)) {
await makeMove(selectedSquare, position)
return
}
selectSquare(position)
} catch (err) {
setError('Failed to make move')
console.error('Move error:', err)
}
}
const handlePieceDrag = async (from, to) => {
if (!isPlayerTurn || isLoading) return
try {
if (validMoves.includes(to)) {
await makeMove(from, to)
}
} catch (err) {
setError('Failed to make move')
console.error('Drag move error:', err)
}
}
const handleNewGame = async () => {
try {
await createNewGame()
setError(null)
} catch (err) {
setError('Failed to create new game')
console.error('New game error:', err)
}
}
const handleUndo = async () => {
try {
await undoLastMove()
setError(null)
} catch (err) {
setError('Failed to undo move')
console.error('Undo error:', err)
}
}
const handleGetHint = async () => {
try {
const hint = await getHint()
// Display hint to user
console.log('Hint:', hint)
} catch (err) {
setError('Failed to get hint')
console.error('Hint error:', err)
}
}
const handleSettingsSave = (newSettings) => {
updateSettings(newSettings)
setShowSettings(false)
}
const closeError = () => {
setError(null)
}
return (
<div className="app">
<header className="app-header">
<nav className="app-nav">
<div className="nav-brand">
<h1>JS Chess</h1>
<span className="framework-badge">React</span>
</div>
<div className="nav-links">
<a href="/">Home</a>
<a href="/vanilla-js">Vanilla JS</a>
<a href="/jquery">jQuery</a>
<a href="/vue">Vue.js</a>
<a href="/react" className="active">React</a>
<a href="/angular">Angular</a>
</div>
</nav>
</header>
<main className="container">
{isLoading && <LoadingSpinner />}
<div className="game-container">
<ChessBoard
board={gameState.board}
selectedSquare={selectedSquare}
validMoves={validMoves}
lastMove={gameState.lastMove}
onSquareClick={handleSquareClick}
onPieceDrag={handlePieceDrag}
theme={settings.boardTheme}
showCoordinates={settings.showCoordinates}
/>
<div className="game-sidebar">
<GameControls
isPlayerTurn={isPlayerTurn}
canUndo={moveHistory.length > 0}
isLoading={isLoading}
onNewGame={handleNewGame}
onUndo={handleUndo}
onGetHint={handleGetHint}
onShowSettings={() => setShowSettings(true)}
/>
<GameStatus
gameState={gameState}
isPlayerTurn={isPlayerTurn}
/>
<MoveHistory
moves={moveHistory}
onJumpToMove={jumpToMove}
/>
</div>
</div>
</main>
{showSettings && (
<SettingsModal
settings={settings}
onSave={handleSettingsSave}
onClose={() => setShowSettings(false)}
/>
)}
{error && (
<ErrorMessage
message={error}
onClose={closeError}
/>
)}
</div>
)
}
export default App
The GameContext.jsx
manages global game state:
// context/GameContext.jsx
import React, { createContext, useContext, useReducer, useCallback } from 'react'
import { chessApi } from '../services/chessApi'
import { gameReducer, initialGameState } from '../reducers/gameReducer'
const GameContext = createContext()
export const useGame = () => {
const context = useContext(GameContext)
if (!context) {
throw new Error('useGame must be used within a GameProvider')
}
return context
}
export const GameProvider = ({ children }) => {
const [state, dispatch] = useReducer(gameReducer, initialGameState)
const createNewGame = useCallback(async () => {
dispatch({ type: 'SET_LOADING', payload: true })
try {
const response = await chessApi.createGame({
ai_enabled: true,
difficulty: 'medium'
})
dispatch({ type: 'GAME_CREATED', payload: response })
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error.message })
throw error
} finally {
dispatch({ type: 'SET_LOADING', payload: false })
}
}, [])
const makeMove = useCallback(async (from, to) => {
if (!state.isPlayerTurn || !state.gameState.id) return
dispatch({ type: 'SET_PLAYER_TURN', payload: false })
try {
const response = await chessApi.makeMove(state.gameState.id, from, to)
if (response.success) {
dispatch({ type: 'MOVE_MADE', payload: response })
// Clear selection
dispatch({ type: 'CLEAR_SELECTION' })
// Check for game end
if (!response.gameState.gameOver) {
// Get AI move
await makeAIMove()
}
}
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error.message })
dispatch({ type: 'SET_PLAYER_TURN', payload: true })
throw error
}
}, [state.isPlayerTurn, state.gameState.id])
const makeAIMove = useCallback(async () => {
try {
const response = await chessApi.getAIMove(state.gameState.id)
if (response.success) {
dispatch({ type: 'AI_MOVE_MADE', payload: response })
dispatch({ type: 'SET_PLAYER_TURN', payload: true })
}
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error.message })
dispatch({ type: 'SET_PLAYER_TURN', payload: true })
}
}, [state.gameState.id])
const undoLastMove = useCallback(async () => {
if (state.moveHistory.length === 0) return
dispatch({ type: 'SET_LOADING', payload: true })
try {
const response = await chessApi.undoMove(state.gameState.id)
if (response.success) {
dispatch({ type: 'MOVE_UNDONE', payload: response })
}
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error.message })
throw error
} finally {
dispatch({ type: 'SET_LOADING', payload: false })
}
}, [state.gameState.id, state.moveHistory.length])
const getHint = useCallback(async () => {
try {
const response = await chessApi.getHint(state.gameState.id)
return response
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error.message })
throw error
}
}, [state.gameState.id])
const selectSquare = useCallback((position) => {
dispatch({ type: 'SELECT_SQUARE', payload: position })
}, [])
const clearSelection = useCallback(() => {
dispatch({ type: 'CLEAR_SELECTION' })
}, [])
const jumpToMove = useCallback((moveIndex) => {
dispatch({ type: 'JUMP_TO_MOVE', payload: moveIndex })
}, [])
const value = {
...state,
createNewGame,
makeMove,
undoLastMove,
getHint,
selectSquare,
clearSelection,
jumpToMove
}
return (
<GameContext.Provider value={value}>
{children}
</GameContext.Provider>
)
}
The gameReducer.js
manages state transitions:
// reducers/gameReducer.js
export const initialGameState = {
gameState: {
id: null,
board: '',
status: 'ready',
activeColor: 'white',
inCheck: false,
gameOver: false,
winner: null,
lastMove: null
},
selectedSquare: null,
validMoves: [],
moveHistory: [],
currentMoveIndex: -1,
isPlayerTurn: true,
isLoading: false,
error: null
}
export const gameReducer = (state, action) => {
switch (action.type) {
case 'SET_LOADING':
return {
...state,
isLoading: action.payload
}
case 'SET_ERROR':
return {
...state,
error: action.payload
}
case 'CLEAR_ERROR':
return {
...state,
error: null
}
case 'GAME_CREATED':
return {
...state,
gameState: action.payload,
selectedSquare: null,
validMoves: [],
moveHistory: [],
currentMoveIndex: -1,
isPlayerTurn: true,
error: null
}
case 'SELECT_SQUARE':
const validMoves = getValidMovesForPosition(action.payload, state.gameState)
return {
...state,
selectedSquare: action.payload,
validMoves
}
case 'CLEAR_SELECTION':
return {
...state,
selectedSquare: null,
validMoves: []
}
case 'MOVE_MADE':
return {
...state,
gameState: action.payload.gameState,
moveHistory: [...state.moveHistory, action.payload.move],
currentMoveIndex: state.moveHistory.length,
selectedSquare: null,
validMoves: []
}
case 'AI_MOVE_MADE':
return {
...state,
gameState: action.payload.gameState,
moveHistory: [...state.moveHistory, action.payload.move],
currentMoveIndex: state.moveHistory.length
}
case 'MOVE_UNDONE':
return {
...state,
gameState: action.payload.gameState,
moveHistory: state.moveHistory.slice(0, -2), // Remove last 2 moves
currentMoveIndex: Math.max(-1, state.currentMoveIndex - 2),
selectedSquare: null,
validMoves: [],
isPlayerTurn: true
}
case 'SET_PLAYER_TURN':
return {
...state,
isPlayerTurn: action.payload
}
case 'JUMP_TO_MOVE':
return {
...state,
currentMoveIndex: action.payload
}
default:
return state
}
}
// Helper function to get valid moves for a position
const getValidMovesForPosition = (position, gameState) => {
// This should come from the API in a real implementation
// For now, return empty array
return []
}
The ChessBoard.jsx
component handles board rendering and interactions:
// components/ChessBoard.jsx
import React, { useMemo, useCallback } from 'react'
import { useDrag, useDrop, DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import Square from './Square'
import Piece from './Piece'
import '../styles/components/ChessBoard.css'
const ChessBoard = ({
board,
selectedSquare,
validMoves,
lastMove,
onSquareClick,
onPieceDrag,
theme = 'classic',
showCoordinates = true
}) => {
const squares = useMemo(() => {
const squareArray = []
for (let rank = 8; rank >= 1; rank--) {
for (let file = 0; file < 8; file++) {
const fileChar = String.fromCharCode(97 + file) // a-h
const position = fileChar + rank
squareArray.push({
position,
file: fileChar,
rank,
isLight: (rank + file) % 2 === 1,
piece: getPieceAt(position, board),
isSelected: selectedSquare === position,
isValidMove: validMoves.includes(position),
isLastMove: lastMove && (lastMove.from === position || lastMove.to === position),
showFileLabel: rank === 1 && showCoordinates,
showRankLabel: file === 0 && showCoordinates
})
}
}
return squareArray
}, [board, selectedSquare, validMoves, lastMove, showCoordinates])
const handleSquareClick = useCallback((position) => {
onSquareClick(position)
}, [onSquareClick])
const handlePieceDrop = useCallback((from, to) => {
onPieceDrag(from, to)
}, [onPieceDrag])
return (
<DndProvider backend={HTML5Backend}>
<div className={`chess-board theme-${theme}`}>
{squares.map((square) => (
<Square
key={square.position}
{...square}
onClick={handleSquareClick}
onPieceDrop={handlePieceDrop}
/>
))}
</div>
</DndProvider>
)
}
// Helper function to get piece at position
const getPieceAt = (position, board) => {
if (!board) return null
// Parse board string and find piece at position
// This implementation depends on the board format from your API
return parseBoardString(board, position)
}
const parseBoardString = (boardString, position) => {
// Implementation depends on board format from API
// This is a placeholder
return null
}
export default ChessBoard
// components/Square.jsx
import React from 'react'
import { useDrop } from 'react-dnd'
import Piece from './Piece'
const Square = ({
position,
file,
rank,
isLight,
piece,
isSelected,
isValidMove,
isLastMove,
showFileLabel,
showRankLabel,
onClick,
onPieceDrop
}) => {
const [{ isOver }, drop] = useDrop({
accept: 'piece',
drop: (item) => {
if (item.position !== position) {
onPieceDrop(item.position, position)
}
},
collect: (monitor) => ({
isOver: monitor.isOver()
})
})
const handleClick = () => {
onClick(position)
}
const squareClasses = [
'square',
isLight ? 'light' : 'dark',
isSelected && 'selected',
isValidMove && 'valid-move',
isLastMove && 'last-move',
isOver && 'drag-over'
].filter(Boolean).join(' ')
return (
<div
ref={drop}
className={squareClasses}
onClick={handleClick}
data-position={position}
>
{showRankLabel && (
<span className="rank-label">{rank}</span>
)}
{showFileLabel && (
<span className="file-label">{file}</span>
)}
{piece && (
<Piece
piece={piece}
position={position}
/>
)}
{isValidMove && !piece && (
<div className="move-indicator" />
)}
</div>
)
}
export default Square
// components/Piece.jsx
import React from 'react'
import { useDrag } from 'react-dnd'
const PIECE_SYMBOLS = {
white: {
king: '♔', queen: '♕', rook: '♖',
bishop: '♗', knight: '♘', pawn: '♙'
},
black: {
king: '♚', queen: '♛', rook: '♜',
bishop: '♝', knight: '♞', pawn: '♟'
}
}
const Piece = ({ piece, position }) => {
const [{ isDragging }, drag] = useDrag({
type: 'piece',
item: { position, piece },
canDrag: () => canDragPiece(piece),
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
})
const pieceColor = getPieceColor(piece)
const pieceType = getPieceType(piece)
const symbol = PIECE_SYMBOLS[pieceColor]?.[pieceType] || ''
const pieceClasses = [
'piece',
isDragging && 'dragging'
].filter(Boolean).join(' ')
return (
<div
ref={drag}
className={pieceClasses}
style={{ opacity: isDragging ? 0.5 : 1 }}
>
{symbol}
</div>
)
}
const canDragPiece = (piece) => {
if (!piece) return false
// Only allow dragging white pieces (player pieces)
return getPieceColor(piece) === 'white'
}
const getPieceColor = (piece) => {
if (typeof piece === 'string') {
return piece === piece.toUpperCase() ? 'white' : 'black'
}
return piece.color || 'white'
}
const getPieceType = (piece) => {
const types = {
'k': 'king', 'q': 'queen', 'r': 'rook',
'b': 'bishop', 'n': 'knight', 'p': 'pawn'
}
if (typeof piece === 'string') {
return types[piece.toLowerCase()] || 'pawn'
}
return piece.type || 'pawn'
}
export default Piece
// hooks/useChessGame.js
import { useState, useCallback, useEffect } from 'react'
import { chessApi } from '../services/chessApi'
export const useChessGame = () => {
const [gameState, setGameState] = useState({
id: null,
board: '',
status: 'ready',
activeColor: 'white',
inCheck: false,
gameOver: false,
winner: null,
lastMove: null
})
const [selectedSquare, setSelectedSquare] = useState(null)
const [validMoves, setValidMoves] = useState([])
const [moveHistory, setMoveHistory] = useState([])
const [isPlayerTurn, setIsPlayerTurn] = useState(true)
const [isLoading, setIsLoading] = useState(false)
const createNewGame = useCallback(async () => {
setIsLoading(true)
try {
const response = await chessApi.createGame({
ai_enabled: true,
difficulty: 'medium'
})
setGameState(response)
setSelectedSquare(null)
setValidMoves([])
setMoveHistory([])
setIsPlayerTurn(true)
return response
} catch (error) {
console.error('Failed to create game:', error)
throw error
} finally {
setIsLoading(false)
}
}, [])
const makeMove = useCallback(async (from, to) => {
if (!isPlayerTurn || !gameState.id) return
setIsPlayerTurn(false)
try {
const response = await chessApi.makeMove(gameState.id, from, to)
if (response.success) {
setGameState(response.gameState)
setMoveHistory(prev => [...prev, response.move])
setSelectedSquare(null)
setValidMoves([])
if (!response.gameState.gameOver) {
// Get AI move
const aiResponse = await chessApi.getAIMove(gameState.id)
if (aiResponse.success) {
setGameState(aiResponse.gameState)
setMoveHistory(prev => [...prev, aiResponse.move])
setIsPlayerTurn(true)
}
}
}
return response
} catch (error) {
setIsPlayerTurn(true)
throw error
}
}, [isPlayerTurn, gameState.id])
const selectSquare = useCallback((position) => {
setSelectedSquare(position)
// Get valid moves for this position
const moves = getValidMovesForPosition(position, gameState)
setValidMoves(moves)
}, [gameState])
const clearSelection = useCallback(() => {
setSelectedSquare(null)
setValidMoves([])
}, [])
const getValidMovesForPosition = (position, state) => {
// This should come from the API
// Placeholder implementation
return []
}
return {
gameState,
selectedSquare,
validMoves,
moveHistory,
isPlayerTurn,
isLoading,
createNewGame,
makeMove,
selectSquare,
clearSelection
}
}
// hooks/useWebSocket.js
import { useEffect, useRef, useCallback } from 'react'
export const useWebSocket = (gameId, onMessage) => {
const ws = useRef(null)
const reconnectAttempts = useRef(0)
const maxReconnectAttempts = 5
const connect = useCallback(() => {
if (!gameId) return
try {
const wsUrl = `ws://localhost:8080/ws/games/${gameId}`
ws.current = new WebSocket(wsUrl)
ws.current.onopen = () => {
console.log('WebSocket connected')
reconnectAttempts.current = 0
}
ws.current.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
onMessage(data)
} catch (error) {
console.error('Failed to parse WebSocket message:', error)
}
}
ws.current.onclose = () => {
console.log('WebSocket disconnected')
// Attempt reconnection
if (reconnectAttempts.current < maxReconnectAttempts) {
setTimeout(() => {
reconnectAttempts.current++
connect()
}, 1000 * Math.pow(2, reconnectAttempts.current))
}
}
ws.current.onerror = (error) => {
console.error('WebSocket error:', error)
}
} catch (error) {
console.error('Failed to create WebSocket connection:', error)
}
}, [gameId, onMessage])
const disconnect = useCallback(() => {
if (ws.current) {
ws.current.close()
ws.current = null
}
}, [])
useEffect(() => {
connect()
return () => {
disconnect()
}
}, [connect, disconnect])
return {
connect,
disconnect
}
}
// components/ErrorBoundary.jsx
import React from 'react'
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false, error: null, errorInfo: null }
}
static getDerivedStateFromError(error) {
return { hasError: true }
}
componentDidCatch(error, errorInfo) {
this.setState({
error,
errorInfo
})
// Log error to monitoring service
console.error('React Error Boundary caught an error:', error, errorInfo)
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong.</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
<button
onClick={() => window.location.reload()}
className="btn btn-primary"
>
Reload Page
</button>
</div>
)
}
return this.props.children
}
}
export default ErrorBoundary
// __tests__/ChessBoard.test.jsx
import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import ChessBoard from '../components/ChessBoard'
const renderWithDnd = (component) => {
return render(
<DndProvider backend={HTML5Backend}>
{component}
</DndProvider>
)
}
describe('ChessBoard', () => {
const mockProps = {
board: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR',
selectedSquare: null,
validMoves: [],
lastMove: null,
onSquareClick: jest.fn(),
onPieceDrag: jest.fn()
}
test('renders chess board with 64 squares', () => {
renderWithDnd(<ChessBoard {...mockProps} />)
const squares = screen.getAllByTestId(/square-/)
expect(squares).toHaveLength(64)
})
test('calls onSquareClick when square is clicked', () => {
renderWithDnd(<ChessBoard {...mockProps} />)
const square = screen.getByTestId('square-e4')
fireEvent.click(square)
expect(mockProps.onSquareClick).toHaveBeenCalledWith('e4')
})
test('highlights selected square', () => {
const props = { ...mockProps, selectedSquare: 'e4' }
renderWithDnd(<ChessBoard {...props} />)
const square = screen.getByTestId('square-e4')
expect(square).toHaveClass('selected')
})
})
// __tests__/useChessGame.test.js
import { renderHook, act } from '@testing-library/react'
import { useChessGame } from '../hooks/useChessGame'
import { chessApi } from '../services/chessApi'
jest.mock('../services/chessApi')
describe('useChessGame', () => {
beforeEach(() => {
jest.clearAllMocks()
})
test('creates new game', async () => {
const mockGame = {
id: 1,
board: 'starting-position',
status: 'in_progress'
}
chessApi.createGame.mockResolvedValue(mockGame)
const { result } = renderHook(() => useChessGame())
await act(async () => {
await result.current.createNewGame()
})
expect(result.current.gameState).toEqual(mockGame)
expect(chessApi.createGame).toHaveBeenCalledWith({
ai_enabled: true,
difficulty: 'medium'
})
})
})
import React, { memo, useMemo, useCallback } from 'react'
const ChessSquare = memo(({ position, piece, isSelected, onClick }) => {
const handleClick = useCallback(() => {
onClick(position)
}, [position, onClick])
const squareClasses = useMemo(() => {
return [
'square',
isSelected && 'selected'
].filter(Boolean).join(' ')
}, [isSelected])
return (
<div className={squareClasses} onClick={handleClick}>
{piece && <Piece piece={piece} />}
</div>
)
})
export default ChessSquare
import React, { Suspense, lazy } from 'react'
const SettingsModal = lazy(() => import('./SettingsModal'))
const MoveHistory = lazy(() => import('./MoveHistory'))
function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<SettingsModal />
<MoveHistory />
</Suspense>
</div>
)
}
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
port: 3004,
host: true
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
dnd: ['react-dnd', 'react-dnd-html5-backend']
}
}
}
},
resolve: {
alias: {
'@': '/src'
}
}
})
- Use TypeScript for better type safety
- Implement proper error boundaries
- Memoize expensive computations with useMemo
- Use useCallback for event handlers
- Split components into smaller, focused pieces
- Test components thoroughly with React Testing Library
- Use context sparingly to avoid unnecessary re-renders
- Review the API Integration Guide for backend communication
- Check the Chess Features Guide for advanced chess logic
- See the UI/UX Guide for design improvements
- Compare with other framework implementations
The React implementation demonstrates how modern component-based architectures can create maintainable, testable chess applications with excellent developer experience and performance.