diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-03-20 23:28:39 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-03-20 23:28:39 +0900 |
| commit | 1afd2781818ef5cba0f018811f12cd8653da10b6 (patch) | |
| tree | f04146c6e8753b07d6d29765f615ab9094cd3f54 /frontend/app | |
| parent | a92aa377d536fe67fa1ff485566da38cf94328bb (diff) | |
| download | phperkaigi-2025-albatross-1afd2781818ef5cba0f018811f12cd8653da10b6.tar.gz phperkaigi-2025-albatross-1afd2781818ef5cba0f018811f12cd8653da10b6.tar.zst phperkaigi-2025-albatross-1afd2781818ef5cba0f018811f12cd8653da10b6.zip | |
fix(frontend): fix state corruption
Diffstat (limited to 'frontend/app')
| -rw-r--r-- | frontend/app/components/Gaming/RankingTable.tsx | 13 | ||||
| -rw-r--r-- | frontend/app/components/GolfPlayApp.tsx | 15 | ||||
| -rw-r--r-- | frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx | 2 | ||||
| -rw-r--r-- | frontend/app/components/GolfWatchApp.tsx | 24 | ||||
| -rw-r--r-- | frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx | 10 | ||||
| -rw-r--r-- | frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx | 7 | ||||
| -rw-r--r-- | frontend/app/routes/golf.$gameId.play.tsx | 31 | ||||
| -rw-r--r-- | frontend/app/routes/golf.$gameId.watch.tsx | 32 |
8 files changed, 77 insertions, 57 deletions
diff --git a/frontend/app/components/Gaming/RankingTable.tsx b/frontend/app/components/Gaming/RankingTable.tsx index e712ed9..a1e41f5 100644 --- a/frontend/app/components/Gaming/RankingTable.tsx +++ b/frontend/app/components/Gaming/RankingTable.tsx @@ -1,11 +1,6 @@ +import { useAtomValue } from "jotai"; import React from "react"; -import type { components } from "../../api/schema"; - -type RankingEntry = components["schemas"]["RankingEntry"]; - -type Props = { - ranking: RankingEntry[]; -}; +import { rankingAtom } from "../../states/watch"; function TableHeaderCell({ children }: { children: React.ReactNode }) { return ( @@ -33,7 +28,9 @@ function formatUnixTimestamp(timestamp: number) { return `${year}-${month}-${day} ${hours}:${minutes}`; } -export default function RankingTable({ ranking }: Props) { +export default function RankingTable() { + const ranking = useAtomValue(rankingAtom); + return ( <div className="overflow-hidden border-2 border-blue-600 rounded-xl"> <table className="min-w-full divide-y divide-gray-400 border-collapse"> diff --git a/frontend/app/components/GolfPlayApp.tsx b/frontend/app/components/GolfPlayApp.tsx index 0bb66eb..97f7cc4 100644 --- a/frontend/app/components/GolfPlayApp.tsx +++ b/frontend/app/components/GolfPlayApp.tsx @@ -1,4 +1,5 @@ import { useAtomValue, useSetAtom } from "jotai"; +import { useHydrateAtoms } from "jotai/utils"; import { useContext, useEffect, useState } from "react"; import { useTimer } from "react-use-precision-timer"; import { useDebouncedCallback } from "use-debounce"; @@ -15,6 +16,7 @@ import { handleSubmitCodePostAtom, handleSubmitCodePreAtom, setCurrentTimestampAtom, + setDurationSecondsAtom, setGameStartedAtAtom, setLatestGameStateAtom, } from "../states/play"; @@ -26,14 +28,21 @@ import GolfPlayAppWaiting from "./GolfPlayApps/GolfPlayAppWaiting"; type Game = components["schemas"]["Game"]; type User = components["schemas"]["User"]; +type LatestGameState = components["schemas"]["LatestGameState"]; type Props = { game: Game; player: User; - initialCode: string; + initialGameState: LatestGameState; }; -export default function GolfPlayApp({ game, player, initialCode }: Props) { +export default function GolfPlayApp({ game, player, initialGameState }: Props) { + useHydrateAtoms([ + [setDurationSecondsAtom, game.duration_seconds], + [setGameStartedAtAtom, game.started_at ?? null], + [setLatestGameStateAtom, initialGameState], + ]); + const apiAuthToken = useContext(ApiAuthTokenContext); const gameStateKind = useAtomValue(gameStateKindAtom); @@ -131,7 +140,7 @@ export default function GolfPlayApp({ game, player, initialCode }: Props) { problemTitle={game.problem.title} problemDescription={game.problem.description} sampleCode={game.problem.sample_code} - initialCode={initialCode} + initialCode={initialGameState.code} onCodeChange={onCodeChange} onCodeSubmit={onCodeSubmit} /> diff --git a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx index c4bd772..b9b3159 100644 --- a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx +++ b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx @@ -2,10 +2,10 @@ import { useAtomValue } from "jotai"; import React, { useRef, useState } from "react"; import { Link } from "react-router"; import { + calcCodeSize, gamingLeftTimeSecondsAtom, scoreAtom, statusAtom, - calcCodeSize, } from "../../states/play"; import type { PlayerProfile } from "../../types/PlayerProfile"; import BorderedContainer from "../BorderedContainer"; diff --git a/frontend/app/components/GolfWatchApp.tsx b/frontend/app/components/GolfWatchApp.tsx index 5f23cdd..185f41d 100644 --- a/frontend/app/components/GolfWatchApp.tsx +++ b/frontend/app/components/GolfWatchApp.tsx @@ -1,4 +1,5 @@ -import { useAtom, useAtomValue, useSetAtom } from "jotai"; +import { useAtomValue, useSetAtom } from "jotai"; +import { useHydrateAtoms } from "jotai/utils"; import { useContext, useEffect, useState } from "react"; import { useTimer } from "react-use-precision-timer"; import { @@ -12,6 +13,7 @@ import { gameStateKindAtom, rankingAtom, setCurrentTimestampAtom, + setDurationSecondsAtom, setGameStartedAtAtom, setLatestGameStatesAtom, } from "../states/watch"; @@ -23,19 +25,34 @@ import GolfWatchAppWaiting1v1 from "./GolfWatchApps/GolfWatchAppWaiting1v1"; import GolfWatchAppWaitingMultiplayer from "./GolfWatchApps/GolfWatchAppWaitingMultiplayer"; type Game = components["schemas"]["Game"]; +type LatestGameState = components["schemas"]["LatestGameState"]; +type RankingEntry = components["schemas"]["RankingEntry"]; export type Props = { game: Game; + initialGameStates: { [key: string]: LatestGameState }; + initialRanking: RankingEntry[]; }; -export default function GolfWatchApp({ game }: Props) { +export default function GolfWatchApp({ + game, + initialGameStates, + initialRanking, +}: Props) { + useHydrateAtoms([ + [rankingAtom, initialRanking], + [setDurationSecondsAtom, game.duration_seconds], + [setGameStartedAtAtom, game.started_at ?? null], + [setLatestGameStatesAtom, initialGameStates], + ]); + const apiAuthToken = useContext(ApiAuthTokenContext); const gameStateKind = useAtomValue(gameStateKindAtom); const setGameStartedAt = useSetAtom(setGameStartedAtAtom); const setCurrentTimestamp = useSetAtom(setCurrentTimestampAtom); const setLatestGameStates = useSetAtom(setLatestGameStatesAtom); - const [ranking, setRanking] = useAtom(rankingAtom); + const setRanking = useSetAtom(rankingAtom); useTimer({ delay: 1000, startImmediately: true }, setCurrentTimestamp); @@ -135,7 +152,6 @@ export default function GolfWatchApp({ game }: Props) { ) : ( <GolfWatchAppGamingMultiplayer gameDisplayName={game.display_name} - ranking={ranking} problemTitle={game.problem.title} problemDescription={game.problem.description} sampleCode={game.problem.sample_code} diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx index 981f533..5b975e7 100644 --- a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx +++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx @@ -1,19 +1,19 @@ import { useAtomValue } from "jotai"; import { + calcCodeSize, gamingLeftTimeSecondsAtom, latestGameStatesAtom, - calcCodeSize, } from "../../states/watch"; import type { PlayerProfile } from "../../types/PlayerProfile"; import BorderedContainer from "../BorderedContainer"; -import SubmitStatusLabel from "../SubmitStatusLabel"; -import ThreeColumnLayout from "../ThreeColumnLayout"; -import TitledColumn from "../TitledColumn"; -import UserIcon from "../UserIcon"; import CodeBlock from "../Gaming/CodeBlock"; import LeftTime from "../Gaming/LeftTime"; import ProblemColumn from "../Gaming/ProblemColumn"; import ScoreBar from "../Gaming/ScoreBar"; +import SubmitStatusLabel from "../SubmitStatusLabel"; +import ThreeColumnLayout from "../ThreeColumnLayout"; +import TitledColumn from "../TitledColumn"; +import UserIcon from "../UserIcon"; type Props = { gameDisplayName: string; diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx index a6b9464..b1d6520 100644 --- a/frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx +++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx @@ -1,5 +1,4 @@ import { useAtomValue } from "jotai"; -import type { components } from "../../api/schema"; import { gamingLeftTimeSecondsAtom } from "../../states/watch"; import LeftTime from "../Gaming/LeftTime"; import ProblemColumn from "../Gaming/ProblemColumn"; @@ -7,11 +6,8 @@ import RankingTable from "../Gaming/RankingTable"; import TitledColumn from "../TitledColumn"; import TwoColumnLayout from "../TwoColumnLayout"; -type RankingEntry = components["schemas"]["RankingEntry"]; - type Props = { gameDisplayName: string; - ranking: RankingEntry[]; problemTitle: string; problemDescription: string; sampleCode: string; @@ -19,7 +15,6 @@ type Props = { export default function GolfWatchAppGamingMultiplayer({ gameDisplayName, - ranking, problemTitle, problemDescription, sampleCode, @@ -43,7 +38,7 @@ export default function GolfWatchAppGamingMultiplayer({ sampleCode={sampleCode} /> <TitledColumn title="順位表"> - <RankingTable ranking={ranking} /> + <RankingTable /> </TitledColumn> </TwoColumnLayout> </div> diff --git a/frontend/app/routes/golf.$gameId.play.tsx b/frontend/app/routes/golf.$gameId.play.tsx index 1ffe45e..4f8468d 100644 --- a/frontend/app/routes/golf.$gameId.play.tsx +++ b/frontend/app/routes/golf.$gameId.play.tsx @@ -1,4 +1,5 @@ -import { useHydrateAtoms } from "jotai/utils"; +import { Provider as JotaiProvider, createStore } from "jotai"; +import { useMemo } from "react"; import type { LoaderFunctionArgs, MetaFunction } from "react-router"; import { useLoaderData } from "react-router"; import { ensureUserLoggedIn } from "../.server/auth"; @@ -8,11 +9,6 @@ import { apiGetGamePlayLatestState, } from "../api/client"; import GolfPlayApp from "../components/GolfPlayApp"; -import { - setDurationSecondsAtom, - setGameStartedAtAtom, - setLatestGameStateAtom, -} from "../states/play"; export const meta: MetaFunction<typeof loader> = ({ data }) => [ { @@ -48,15 +44,22 @@ export default function GolfPlay() { const { apiAuthToken, game, player, gameState } = useLoaderData<typeof loader>(); - useHydrateAtoms([ - [setDurationSecondsAtom, game.duration_seconds], - [setGameStartedAtAtom, game.started_at ?? null], - [setLatestGameStateAtom, gameState], - ]); + const store = useMemo(() => { + void game.game_id; + void player.user_id; + return createStore(); + }, [game.game_id, player.user_id]); return ( - <ApiAuthTokenContext.Provider value={apiAuthToken}> - <GolfPlayApp game={game} player={player} initialCode={gameState.code} /> - </ApiAuthTokenContext.Provider> + <JotaiProvider store={store}> + <ApiAuthTokenContext.Provider value={apiAuthToken}> + <GolfPlayApp + key={game.game_id} + game={game} + player={player} + initialGameState={gameState} + /> + </ApiAuthTokenContext.Provider> + </JotaiProvider> ); } diff --git a/frontend/app/routes/golf.$gameId.watch.tsx b/frontend/app/routes/golf.$gameId.watch.tsx index 42bde52..cd01b17 100644 --- a/frontend/app/routes/golf.$gameId.watch.tsx +++ b/frontend/app/routes/golf.$gameId.watch.tsx @@ -1,4 +1,5 @@ -import { useHydrateAtoms } from "jotai/utils"; +import { Provider as JotaiProvider, createStore } from "jotai"; +import { useMemo } from "react"; import type { LoaderFunctionArgs, MetaFunction } from "react-router"; import { useLoaderData } from "react-router"; import { ensureUserLoggedIn } from "../.server/auth"; @@ -9,12 +10,6 @@ import { apiGetGameWatchRanking, } from "../api/client"; import GolfWatchApp from "../components/GolfWatchApp"; -import { - rankingAtom, - setDurationSecondsAtom, - setGameStartedAtAtom, - setLatestGameStatesAtom, -} from "../states/watch"; export const meta: MetaFunction<typeof loader> = ({ data }) => [ { @@ -57,16 +52,21 @@ export default function GolfWatch() { const { apiAuthToken, game, ranking, gameStates } = useLoaderData<typeof loader>(); - useHydrateAtoms([ - [rankingAtom, ranking], - [setDurationSecondsAtom, game.duration_seconds], - [setGameStartedAtAtom, game.started_at ?? null], - [setLatestGameStatesAtom, gameStates], - ]); + const store = useMemo(() => { + void game.game_id; + return createStore(); + }, [game.game_id]); return ( - <ApiAuthTokenContext.Provider value={apiAuthToken}> - <GolfWatchApp game={game} /> - </ApiAuthTokenContext.Provider> + <JotaiProvider store={store}> + <ApiAuthTokenContext.Provider value={apiAuthToken}> + <GolfWatchApp + key={game.game_id} + game={game} + initialGameStates={gameStates} + initialRanking={ranking} + /> + </ApiAuthTokenContext.Provider> + </JotaiProvider> ); } |
