Skip to content

Instantly share code, notes, and snippets.

@BornPsych
Created December 10, 2025 10:30
Show Gist options
  • Select an option

  • Save BornPsych/3e7bf8a22fc23351b6df55fba2384516 to your computer and use it in GitHub Desktop.

Select an option

Save BornPsych/3e7bf8a22fc23351b6df55fba2384516 to your computer and use it in GitHub Desktop.

CasinoBet.jsx Refactoring Plan

Date: 2025-12-10


Overview

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.


Current Issues Identified

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

Phase 1: Fix the Result Flashing Bug (Do This First)

Step 1.1: Add null check to winner display logic

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.

Step 1.2: Ensure initial fetch sets lastResultMid BEFORE winner check runs

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
) {

Step 1.3: Reset hasInitializedResults on game switch

Location: Lines 2254-2287 (game switch useEffect)

Add to the reset logic:

setHasInitializedResults(false);

Phase 2: Extract Custom Hooks

Step 2.1: Create useCasinoWebSocket hook

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

Step 2.2: Create useCasinoBetting hook

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

Step 2.3: Create useCasinoResults hook

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

Step 2.4: Create useCasinoPolling hook (optional, if not merged into WebSocket hook)

Create file: client/src/hooks/useCasinoPolling.js

Extract: Lines 360-379 (fallback API polling)


Phase 3: Extract UI Components

Step 3.1: Create QuickAmountGrid component

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>
  );
}

Step 3.2: Create BetControlPanel component

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
}

Step 3.3: Create BetBox component

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
}

Step 3.4: Create CardsDisplay component

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
}

Step 3.5: Create WinnerPopup component

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>
  );
}

Step 3.6: Create VideoStream component

Create file: client/src/components/casino/VideoStream.jsx

Extract from CasinoBet.jsx: Lines 2388-2399+ (VideoStream inline component)


Phase 4: Extract Game-Specific UI Components

Step 4.1: Create game renderer components

Create folder: client/src/components/casino/games/

Create files:

  • TeenPattiGame.jsx - For teenmuf, teen20, teen6, etc.
  • PokerGame.jsx - For poker20, teensin
  • BaccaratGame.jsx - For baccarat, baccarat2
  • DefaultCasinoGame.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) => {},
}

Step 4.2: Create GameRenderer factory component

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} />;
}

Phase 5: Refactored CasinoBet.jsx Structure

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)


Phase 6: Implementation Order (Step-by-Step)

Week 1: Bug Fix + First Hook

  1. Day 1-2: Implement Phase 1 (bug fix)

    • Add hasInitializedResults state
    • Update winner display check
    • Test thoroughly
  2. Day 3-4: Create useCasinoResults hook (Phase 2.3)

    • This directly relates to the bug fix
    • Move result-related state and effects
    • Test game switching behavior

Week 2: Core Hooks

  1. Day 1-2: Create useCasinoWebSocket hook (Phase 2.1)

    • Move WebSocket connection logic
    • Move fallback polling
    • Test real-time updates
  2. Day 3-4: Create useCasinoBetting hook (Phase 2.2)

    • Move betting state and functions
    • Test bet placement flow

Week 3: UI Components

  1. Day 1: Extract QuickAmountGrid and BetControlPanel (Phase 3.1, 3.2)
  2. Day 2: Extract CardsDisplay and BetBox (Phase 3.3, 3.4)
  3. Day 3: Extract WinnerPopup and VideoStream (Phase 3.5, 3.6)

Week 4: Game Components

  1. Day 1-2: Extract game-specific components (Phase 4.1)

    • Start with TeenPattiGame (most used)
    • Then BaccaratGame
    • Then others
  2. Day 3: Create GameRenderer factory (Phase 4.2)

  3. Day 4: Final cleanup and testing


File Structure After Refactoring

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)

Testing Checklist

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

Notes

  • 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

What You Could Explore More

  1. Add unit tests for each extracted hook using React Testing Library
  2. Implement Zustand or Jotai instead of many useState for related state
  3. Add error boundaries around game components
  4. Implement lazy loading for game-specific components
  5. Create a state machine (XState) for game flow states
  6. Add performance monitoring with React Profiler
  7. Consider WebSocket reconnection strategy with exponential backoff
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment