From 15d84312de0f16acfc8c36a3996f3ad560cc0506 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Jan 2026 03:06:30 +0000 Subject: [PATCH] fix: Prevent React hydration error #418 on game setup page Added hasMounted state guard to avoid store value mismatch between server and client renders. The game setup page was reading from useUIStore directly (which loads from localStorage on client), causing text content like music/fullscreen icons to differ between SSR and hydration. This matches the pattern already used on the home page. https://claude.ai/code/session_01WEUBMGfhkYYs3qmkDjtEgT --- src/app/game/setup/page.tsx | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/app/game/setup/page.tsx b/src/app/game/setup/page.tsx index 657b493d..a3701fd3 100644 --- a/src/app/game/setup/page.tsx +++ b/src/app/game/setup/page.tsx @@ -293,13 +293,24 @@ function SettingSelect({ export default function GameSetupPage() { const router = useRouter(); - const musicEnabled = useUIStore((state) => state.musicEnabled); + const [hasMounted, setHasMounted] = useState(false); + + const musicEnabledStore = useUIStore((state) => state.musicEnabled); const musicVolume = useUIStore((state) => state.musicVolume); const toggleMusic = useUIStore((state) => state.toggleMusic); - const isFullscreen = useUIStore((state) => state.isFullscreen); + const isFullscreenStore = useUIStore((state) => state.isFullscreen); const toggleFullscreen = useUIStore((state) => state.toggleFullscreen); const setFullscreen = useUIStore((state) => state.setFullscreen); + // Use default values during SSR/hydration to avoid mismatch, then sync after mount + const musicEnabled = hasMounted ? musicEnabledStore : true; + const isFullscreen = hasMounted ? isFullscreenStore : false; + + // Mark as mounted after hydration + useEffect(() => { + setHasMounted(true); + }, []); + // Join lobby modal state const [showJoinModal, setShowJoinModal] = useState(false); const [showLobbyBrowser, setShowLobbyBrowser] = useState(false); @@ -310,14 +321,14 @@ export default function GameSetupPage() { const handleMusicToggle = useCallback(() => { toggleMusic(); - const newEnabled = !musicEnabled; + const newEnabled = !musicEnabledStore; MusicPlayer.setMuted(!newEnabled); if (!newEnabled) { MusicPlayer.pause(); } else { MusicPlayer.resume(); } - }, [toggleMusic, musicEnabled]); + }, [toggleMusic, musicEnabledStore]); // Continue menu music (or start if navigated directly here) // Also start preloading 3D assets in background while player is in lobby @@ -343,11 +354,11 @@ export default function GameSetupPage() { // Don't stop on unmount - music stops when game starts }, []); - // Sync volume changes + // Sync volume changes - use store value directly since MusicPlayer only runs client-side useEffect(() => { MusicPlayer.setVolume(musicVolume); - MusicPlayer.setMuted(!musicEnabled); - }, [musicVolume, musicEnabled]); + MusicPlayer.setMuted(!musicEnabledStore); + }, [musicVolume, musicEnabledStore]); // Sync fullscreen state with browser useEffect(() => {