Date: 2025-12-10
Current State: CasinoBet.jsx is ~2400 lines with 20+ useState hooks, 15+ useEffect hooks, and multiple inline components. This causes maintenance issues and the result flashing bug.
Goal: Transform into a modular, extensible architecture with custom hooks and separated components.
| Issue | Location | Impact |
|---|---|---|
| Result flashing bug | Lines 2332-2337 | Shows previous game results on load |
| 20+ useState scattered | Lines 31-46, 2245-2251 | Hard to track state flow |
| 15+ useEffect hooks | Throughout | Complex lifecycle, race conditions |
| Inline components | Lines 689-711, 713-826, 835-864 | Not reusable, bloated file |
| Game-specific UI in switch | Lines 1069-2228 | 1000+ lines of JSX in one function |
| WebSocket logic inline | Lines 200-350 | Not reusable, hard to test |
File: client/src/Games/CasinoBet.jsx
Location: Lines 2332-2337
Current Code:
if (
currentGameId === gameid &&
lastResultMid !== null &&
lastResultMid !== currentResultMid &&
currentResult.win
) {Change: The lastResultMid !== null check is already there (line 2334). Verify it's working. If results still flash, the issue is in the initial fetch.
File: client/src/Games/CasinoBet.jsx
Location: Lines 2299-2317 (initial fetch useEffect)
Current Code:
dispatch(fetchCasinoResultData(gameid))
.then((result) => {
const firstResultMid = result?.payload?.data?.res?.[0]?.mid;
if (firstResultMid && lastResultMid === null) {
setLastResultMid(firstResultMid);
}
})Verify: This should already prevent showing initial result as "new". If still flashing, add a flag:
// Add state at line 2248
const [hasInitializedResults, setHasInitializedResults] = useState(false);
// Modify initial fetch effect
dispatch(fetchCasinoResultData(gameid))
.then((result) => {
const firstResultMid = result?.payload?.data?.res?.[0]?.mid;
if (firstResultMid) {
setLastResultMid(firstResultMid);
setHasInitializedResults(true); // Mark as initialized
}
})
// Modify winner check (lines 2332-2337)
if (
currentGameId === gameid &&
hasInitializedResults && // Only show after initial load complete
lastResultMid !== null &&
lastResultMid !== currentResultMid &&
currentResult.win
) {Location: Lines 2254-2287 (game switch useEffect)
Add to the reset logic:
setHasInitializedResults(false);Create file: client/src/hooks/useCasinoWebSocket.js
Extract from CasinoBet.jsx: Lines 197-350 (WebSocket connection logic)
Hook signature:
export function useCasinoWebSocket(gameid, bettingDataMid) {
const [isConnected, setIsConnected] = useState(false);
const [bettingData, setBettingData] = useState(null);
// WebSocket connection logic here
return {
isConnected,
bettingData,
subscribe: (gameId) => {},
unsubscribe: (gameId) => {},
};
}What to move:
- Lines 198:
let globalSocket = null - Lines 200-350: WebSocket useEffect
- Lines 360-379: Fallback polling useEffect
Create file: client/src/hooks/useCasinoBetting.js
Extract from CasinoBet.jsx: Betting state and logic
Hook signature:
export function useCasinoBetting(gameid, userInfo) {
const [betAmount, setBetAmount] = useState(0);
const [betOdds, setBetOdds] = useState(1.29);
const [betControl, setBetControl] = useState(null);
const [selectedBet, setSelectedBet] = useState(null);
const [formData, setFormData] = useState({...});
const [loader, setLoader] = useState(false);
const placeBet = async (otype, teamName, maxAmo, xVal) => {...};
const resetBettingState = () => {...};
const setValue = (xValue, teamName, otype, nat, sid) => {...};
const updateAmount = (val, toggle) => {...};
return {
betAmount, setBetAmount,
betOdds, setBetOdds,
betControl, setBetControl,
selectedBet,
formData,
loader,
placeBet,
resetBettingState,
setValue,
updateAmount,
};
}What to move:
- Lines 33-38: Betting state (betOdds, betAmount, selectTeamName, selectedBet, loader, betControl)
- Lines 105-114: formData state
- Lines 66-72: resetBettingState function
- Lines 138-153: updateAmount function
- Lines 381-404: setValue function
- Lines 406-483: placeBet function
Create file: client/src/hooks/useCasinoResults.js
Extract from CasinoBet.jsx: Result display logic
Hook signature:
export function useCasinoResults(gameid) {
const dispatch = useDispatch();
const { resultData, currentGameId } = useSelector(state => state.casino);
const [showWinner, setShowWinner] = useState(false);
const [lastResultMid, setLastResultMid] = useState(null);
const [isGameSwitching, setIsGameSwitching] = useState(false);
const [hasInitializedResults, setHasInitializedResults] = useState(false);
// Game switch cleanup
// Initial result fetch
// Winner display logic
return {
resultData,
currentGameId,
showWinner,
isGameSwitching,
winnerMap: getWinnerMap(gameid),
};
}What to move:
- Lines 2245-2251: Result-related state
- Lines 2231-2243: getWinnerMap function
- Lines 2254-2287: Game switch useEffect
- Lines 2299-2317: Initial fetch useEffect
- Lines 2319-2352: Winner display useEffect
Create file: client/src/hooks/useCasinoPolling.js
Extract: Lines 360-379 (fallback API polling)
Create file: client/src/components/casino/QuickAmountGrid.jsx
Extract from CasinoBet.jsx: Lines 689-711
Component props:
export function QuickAmountGrid({
amounts,
selectedAmount,
onSelect,
formatAmount
}) {
return (
<div className="flex flex-wrap gap-1 ...">
{amounts.map((val) => (
// Coin chip UI
))}
</div>
);
}Create file: client/src/components/casino/BetControlPanel.jsx
Extract from CasinoBet.jsx: Lines 713-826
Component props:
export function BetControlPanel({
betControl,
betAmount,
betOdds,
loading,
quickAmounts,
onCancel,
onOddsChange,
onAmountChange,
onPlaceBet,
}) {
// Control panel UI
}Create file: client/src/components/casino/BetBox.jsx
Extract from CasinoBet.jsx: Lines 835-864
Component props:
export function BetBox({
option,
gradient,
align,
rounded,
height,
onSelect
}) {
// Bet box UI for baccarat-style games
}Create file: client/src/components/casino/CardsDisplay.jsx
Extract from CasinoBet.jsx: Lines 520-614 (renderCardsDisplay function)
Component props:
export function CardsDisplay({
cards,
gameType,
roundId
}) {
// Card display UI
}Create file: client/src/components/casino/WinnerPopup.jsx
Component props:
export function WinnerPopup({
show,
winner,
winnerMap,
onClose
}) {
if (!show) return null;
return (
<div className="winner-popup-overlay">
<div className="winner-content">
Winner: {winnerMap[winner]}
</div>
</div>
);
}Create file: client/src/components/casino/VideoStream.jsx
Extract from CasinoBet.jsx: Lines 2388-2399+ (VideoStream inline component)
Create folder: client/src/components/casino/games/
Create files:
TeenPattiGame.jsx- For teenmuf, teen20, teen6, etc.PokerGame.jsx- For poker20, teensinBaccaratGame.jsx- For baccarat, baccarat2DefaultCasinoGame.jsx- For games with back/lay
Extract from CasinoBet.jsx: Lines 1069-2228 (renderGameUI switch statement)
Each component receives:
{
bettingData,
groupedData,
betControl,
betAmount,
betOdds,
pendingBetAmounts,
selectedTeamSubtype,
onBetSelect: (option, type) => {},
onPlaceBet: (type, name, max, odds) => {},
}Create file: client/src/components/casino/GameRenderer.jsx
import TeenPattiGame from './games/TeenPattiGame';
import PokerGame from './games/PokerGame';
import BaccaratGame from './games/BaccaratGame';
import DefaultCasinoGame from './games/DefaultCasinoGame';
const GAME_COMPONENTS = {
teenmuf: TeenPattiGame,
teen20: TeenPattiGame,
poker20: PokerGame,
teensin: PokerGame,
baccarat: BaccaratGame,
baccarat2: BaccaratGame,
};
export function GameRenderer({ gameid, ...props }) {
const Component = GAME_COMPONENTS[gameid] || DefaultCasinoGame;
return <Component gameid={gameid} {...props} />;
}After all extractions, CasinoBet.jsx should look like:
import { useCasinoWebSocket } from '../hooks/useCasinoWebSocket';
import { useCasinoBetting } from '../hooks/useCasinoBetting';
import { useCasinoResults } from '../hooks/useCasinoResults';
import { GameRenderer } from '../components/casino/GameRenderer';
import { CardsDisplay } from '../components/casino/CardsDisplay';
import { WinnerPopup } from '../components/casino/WinnerPopup';
import { VideoStream } from '../components/casino/VideoStream';
import { RecentResults } from '../components/casino/RecentResults';
export default function CasinoBet() {
const { gameid } = useParams();
const dispatch = useDispatch();
// Custom hooks
const { isConnected, bettingData } = useCasinoWebSocket(gameid);
const betting = useCasinoBetting(gameid, userInfo);
const results = useCasinoResults(gameid);
// Redux selectors
const { userInfo } = useSelector(state => state.auth);
const { pendingBet, pendingBetAmounts } = useSelector(state => state.bet);
// Minimal component-specific state
const [selectedTeamSubtype, setSelectedTeamSubtype] = useState(null);
return (
<div className="casino-container">
{/* Video Stream */}
<VideoStream gameid={gameid} />
{/* Cards Display */}
<CardsDisplay
cards={bettingData?.card}
gameType={bettingData?.gtype}
roundId={bettingData?.mid}
/>
{/* Game-specific UI */}
<GameRenderer
gameid={gameid}
bettingData={bettingData}
betting={betting}
pendingBetAmounts={pendingBetAmounts}
selectedTeamSubtype={selectedTeamSubtype}
onSubtypeSelect={setSelectedTeamSubtype}
/>
{/* Recent Results */}
<RecentResults
results={results.resultData}
gameid={gameid}
currentGameId={results.currentGameId}
/>
{/* Winner Popup */}
<WinnerPopup
show={results.showWinner}
winner={results.resultData?.res?.[0]?.win}
winnerMap={results.winnerMap}
/>
</div>
);
}Target: ~150-200 lines (down from ~2400)
-
Day 1-2: Implement Phase 1 (bug fix)
- Add
hasInitializedResultsstate - Update winner display check
- Test thoroughly
- Add
-
Day 3-4: Create
useCasinoResultshook (Phase 2.3)- This directly relates to the bug fix
- Move result-related state and effects
- Test game switching behavior
-
Day 1-2: Create
useCasinoWebSockethook (Phase 2.1)- Move WebSocket connection logic
- Move fallback polling
- Test real-time updates
-
Day 3-4: Create
useCasinoBettinghook (Phase 2.2)- Move betting state and functions
- Test bet placement flow
- Day 1: Extract
QuickAmountGridandBetControlPanel(Phase 3.1, 3.2) - Day 2: Extract
CardsDisplayandBetBox(Phase 3.3, 3.4) - Day 3: Extract
WinnerPopupandVideoStream(Phase 3.5, 3.6)
-
Day 1-2: Extract game-specific components (Phase 4.1)
- Start with
TeenPattiGame(most used) - Then
BaccaratGame - Then others
- Start with
-
Day 3: Create
GameRendererfactory (Phase 4.2) -
Day 4: Final cleanup and testing
client/src/
├── hooks/
│ ├── useCasinoWebSocket.js # WebSocket + polling logic
│ ├── useCasinoBetting.js # Betting state + functions
│ ├── useCasinoResults.js # Results + winner display
│ └── useWebRTCConnection.js # (existing)
│
├── components/
│ └── casino/
│ ├── QuickAmountGrid.jsx # Chip selection UI
│ ├── BetControlPanel.jsx # Bet placement controls
│ ├── BetBox.jsx # Single bet option box
│ ├── CardsDisplay.jsx # Current cards display
│ ├── WinnerPopup.jsx # Winner announcement
│ ├── VideoStream.jsx # Video iframe wrapper
│ ├── RecentResults.jsx # Results history bar
│ ├── GameRenderer.jsx # Game component factory
│ └── games/
│ ├── TeenPattiGame.jsx # Teen Patti variants
│ ├── PokerGame.jsx # Poker variants
│ ├── BaccaratGame.jsx # Baccarat variants
│ └── DefaultCasinoGame.jsx
│
├── Games/
│ └── CasinoBet.jsx # ~150 lines (orchestrator only)
After each phase, verify:
- Game loads without errors
- WebSocket connects and receives updates
- Betting data displays correctly
- Bet placement works
- Results update when round changes
- Winner popup shows for NEW results only (not on page load)
- Game switching clears old state
- Fallback polling works when WebSocket disconnects
- Don't skip Phase 1 - Fix the bug first before major refactoring
- Test after each extraction - Don't batch multiple changes
- Keep backward compatibility - Old imports should still work during transition
- Use TypeScript interfaces (optional) - Define prop types for new components
- Add unit tests for each extracted hook using React Testing Library
- Implement Zustand or Jotai instead of many useState for related state
- Add error boundaries around game components
- Implement lazy loading for game-specific components
- Create a state machine (XState) for game flow states
- Add performance monitoring with React Profiler
- Consider WebSocket reconnection strategy with exponential backoff