From 169b21d6a8f389361a57e8231b1532211ba5cb21 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 23 Jul 2026 08:22:00 -0400 Subject: [PATCH] Fix hooks order in Table (run replay-step hooks before the loading guard). --- web/src/components/Table.tsx | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/web/src/components/Table.tsx b/web/src/components/Table.tsx index 5e1e524..0988a11 100644 --- a/web/src/components/Table.tsx +++ b/web/src/components/Table.tsx @@ -14,22 +14,12 @@ import { EventLog, battleLogLines } from './EventLog' export function Table({ session, onLeave }: { session: Session; onLeave: () => void }) { const { view, error, connected, send } = useGame(session) - if (!view) { - return ( -
- {connected ? 'Loading game…' : 'Connecting…'} -
- ) - } - - const you = view.players[view.youSeat] - const opponents = view.players.filter((p) => p.seat !== view.youSeat) - // Battle replay step lives here (not inside BattlePhase) so the event log, // a sibling, can render the battle narration up to the same step. Deriving // the effective step from the current battle round resets it to 0 whenever a - // new battle arrives, without a separate effect. - const battleRound = view.battle?.round ?? -1 + // new battle arrives, without a separate effect. These hooks must run on + // every render (before any early return) to satisfy the rules of hooks. + const battleRound = view?.battle?.round ?? -1 const [stepState, setStepState] = useState<{ round: number; step: number }>({ round: -1, step: 0, @@ -44,12 +34,23 @@ export function Table({ session, onLeave }: { session: Session; onLeave: () => v const battleLines = useMemo( () => - view.phase === 'battle' && view.battle?.events + view?.phase === 'battle' && view.battle?.events ? battleLogLines(view.battle.events, step) : undefined, - [view.phase, view.battle, step], + [view, step], ) + if (!view) { + return ( +
+ {connected ? 'Loading game…' : 'Connecting…'} +
+ ) + } + + const you = view.players[view.youSeat] + const opponents = view.players.filter((p) => p.seat !== view.youSeat) + return (