Conversation
| }); | ||
|
|
||
| return <RankingTabs />; |
There was a problem hiding this comment.
🔴 Missing [] dependency array causes play() to re-fire on every render, restarting BGM
In Ranking.tsx, useEffect(() => { play("/sounds/bgm0.mp3"); }) has no dependency array, so it runs after every render, not just on mount. Each call to play (in WebAudioPlayer.tsx:55) first calls stop() to halt the current audio, then restarts the track from the beginning. Since play and stop in WebAudioPlayer.tsx:80 are not memoized with useCallback, every render of WebAudioPlayer produces a new context value, triggering consumer re-renders — which in turn re-fire this effect. Concretely, toggling the sound button causes the ranking BGM to restart from the beginning, and any parent re-render will do the same.
| }); | |
| return <RankingTabs />; | |
| useEffect(() => { | |
| play("/sounds/bgm0.mp3"); | |
| }, []); |
Was this helpful? React with 👍 or 👎 to provide feedback.
| }); | ||
|
|
||
| return ( |
There was a problem hiding this comment.
🟡 Missing [] dependency array causes stop() to fire on every render
In Home.tsx, useEffect(() => { stop(); }) has no dependency array, so it runs after every render rather than only on mount. While stop() is idempotent when nothing is playing (it checks sourceRef.current), this still means that any re-render caused by context value changes or parent re-renders will unnecessarily invoke stop(). This is inconsistent with the analogous effects in GameResult.tsx:21-23 and GameTyping.tsx:197-211 which correctly use [].
| }); | |
| return ( | |
| useEffect(() => { | |
| stop(); | |
| }, []); |
Was this helpful? React with 👍 or 👎 to provide feedback.
| }; | ||
| }; | ||
|
|
||
| return <PlayerContext.Provider value={{ play, stop }}>{children}</PlayerContext.Provider>; |
There was a problem hiding this comment.
📝 Info: Context value not memoized — causes unnecessary consumer re-renders
In WebAudioPlayer.tsx:80, the value={{ play, stop }} creates a new object on every render since neither play nor stop are wrapped in useCallback. This means every re-render of WebAudioPlayer (e.g., when isPlay changes) creates a new context value, forcing all useContext(PlayerContext) consumers to re-render. This amplifies the impact of the missing dependency arrays in Ranking.tsx and Home.tsx (reported as bugs), and is generally wasteful. Wrapping play and stop in useCallback (and using an isPlayRef for the isPlay check inside play) would stabilize the context value and prevent unnecessary consumer re-renders.
Was this helpful? React with 👍 or 👎 to provide feedback.
| <> | ||
| <Footer isPlay={isPlay} setIsPlay={setIsPlay} /> | ||
| <div className="children"> | ||
| <WebAudioPlayer isPlay={isPlay}>{children}</WebAudioPlayer> | ||
| </div> |
There was a problem hiding this comment.
📝 Info: Footer rendered before children — visual ordering relies on CSS fixed positioning
In client.tsx:11-15, the Footer is rendered before the children div in the DOM. The previous layout in layout.tsx also had Footer before children, so this matches the existing pattern. The footer uses position: fixed (Footer.module.scss:6) so DOM order doesn't affect visual layout. Not a bug, just noting the ordering is intentional and CSS-dependent.
Was this helpful? React with 👍 or 👎 to provide feedback.
| useEffect(() => { | ||
| play("/sounds/bgm1.mp3"); |
There was a problem hiding this comment.
🚩 Stale play closure in game components won't respond to mid-page sound toggle
In GamePre.tsx:11, GameTyping.tsx:199-203, and GameResult.tsx:22, the play function is called inside useEffect hooks with [] or [nextPage] dependency arrays, but play itself is not included as a dependency. Since play captures isPlay via closure (WebAudioPlayer.tsx:53), the version captured at mount time won't reflect later toggles. This means if a user toggles sound ON while already on one of these pages, the BGM won't start (the captured play still has isPlay=false). This is a minor UX limitation rather than a crash-level bug — sound works correctly when navigating to a new page after toggling.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if (!buffer) { | ||
| const response = await fetch(url); | ||
| const arrayBuffer = await response.arrayBuffer(); | ||
| buffer = await ctx.decodeAudioData(arrayBuffer); | ||
| bufferCacheRef.current.set(url, buffer); |
There was a problem hiding this comment.
📝 Info: No error handling in play for failed fetch or decode
In WebAudioPlayer.tsx:61-63, fetch(url) and ctx.decodeAudioData(arrayBuffer) are both async operations that can throw (e.g., 404 response, invalid audio data), but neither is wrapped in a try/catch. An unhandled promise rejection would bubble up silently. Since play is called from useEffect callbacks which don't handle the returned promise, any rejection becomes an unhandled promise rejection warning. Consider wrapping the async body in a try/catch to prevent this.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if (isProcessingRef.current) { | ||
| return; | ||
| } | ||
| stop(); |
There was a problem hiding this comment.
📝 Info: sendResultData uses stop without it in the dependency array
In GameTyping.tsx:57, stop() is called inside sendResultData which is wrapped in useCallback with deps [stats, nextPage, router, setScore] at line 108. stop from useWebAudio() is not included. I verified this is safe because stop (WebAudioPlayer.tsx:33-41) only accesses sourceRef.current, which is a ref and always points to the latest value regardless of closure staleness. So even a stale stop reference correctly stops the current audio source.
Was this helpful? React with 👍 or 👎 to provide feedback.
チケットへのリンク
close #69
やったこと
やらないこと
容量が大きいBGMデータ自体のアップロード
できるようになること(ユーザ目線)
画面右下のボタンでBGMのON/OFFを切り替えられるようになります。暴発を防ぐため初期値はOFF、かつ再読み込みでリセットされます。
できなくなること(ユーザ目線)
無し
動作確認
全機能を目視で確認していますが、レビューされる方はお手元の環境でダブルチェックをお願いします (特に生協指定PCをお持ちの方)
その他
無し