Skip to content

Commit 5d9d4e4

Browse files
dylanjeffersclaude
andauthored
fix(mobile): start track comments fetch on screen mount to cut load delay (#14450)
## Problem On the mobile track detail screen, the comments section takes a while to appear after the rest of the page is up. The root cause is a **serial waterfall** before the comments query can even start. The comments query (`useTrackComments`) lives inside `CommentSectionProvider` → `CommentPreview`, which only mounts after three gates clear in order in [`TrackScreen.tsx`](packages/mobile/src/screens/track-screen/TrackScreen.tsx): 1. `useTrackByParams` resolves the track 2. `useUser(track.owner_id)` resolves — the `if (!track || !user) return <Skeleton/>` early-return blocks `CommentPreview` from mounting, **even though comments don't need user data** 3. `ScreenSecondaryContent` waits for `isPrimaryContentReady` before mounting its children So comments only begin fetching well after track + owner data have loaded and the primary content is painted — not in parallel. There was no waterfall at the *endpoint* level (`GET /tracks/{id}/comments`, no artificial delay), and a skeleton already exists in `CommentPreviewContent` — the issue is purely *when* the fetch starts. ## Fix Add `usePrefetchTrackComments` (`packages/common`) and call it at the top of `TrackScreen`, before the early-return and the secondary-content gate. It fires the comment-list query **on mount, in parallel with the track/user fetch**, as soon as a `trackId` is known — from route params when navigating by id, so it can start *before the track even resolves*. Because the comment-list query uses `gcTime: 0` (evicted the instant no observer is mounted), a one-shot prefetch wouldn't survive. So `usePrefetchTrackComments` keeps a **live observer** mounted at the screen level, using the default `Top` sort + page size to match what `CommentSectionProvider` requests — guaranteeing a cache hit when the comment section finally mounts. Net effect: by the time `CommentPreview` renders, the comments are usually already in cache, so the skeleton rarely flashes and comments appear with (or shortly after) the rest of the page instead of well after it. Comment-disabled tracks are skipped once that flag is known. ### Implementation notes - Extracted the raw infinite query in `useTrackComments.ts` into a shared `useTrackCommentsQuery` base hook so both `useTrackComments` (which adds the error toast + `useComments` hydration) and the new `usePrefetchTrackComments` reuse one definition — no duplicated `queryFn`. Behavior of `useTrackComments` is unchanged. ## Testing - `turbo run typecheck --filter=@audius/common --filter=@audius/mobile` — passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e9ad034 commit 5d9d4e4

4 files changed

Lines changed: 84 additions & 5 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@audius/common': patch
3+
---
4+
5+
Add `usePrefetchTrackComments` and `usePrefetchTrackPageLineup`, hooks that warm a track's comment list and "more by / remixes / you might also like" lineup as early as possible (e.g. on track screen mount) so those sections render from cache instead of starting their own fetch only once they mount. Each keeps a live observer so the warmed data isn't evicted before the section mounts. `usePrefetchTrackComments` can fire from a bare trackId; `usePrefetchTrackPageLineup` still depends on the hero track + owner handle but starts the instant those resolve rather than waiting for the mobile screen-ready/animation gate.

packages/common/src/api/tan-query/comments/useTrackComments.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { useEffect } from 'react'
22

3-
import { Id } from '@audius/sdk'
3+
import {
4+
GetTrackCommentsSortMethodEnum as CommentSortMethod,
5+
Id
6+
} from '@audius/sdk'
47
import {
58
useInfiniteQuery,
69
useIsMutating,
@@ -28,7 +31,13 @@ export type GetCommentsByTrackArgs = {
2831
pageSize?: number
2932
}
3033

31-
export const useTrackComments = (
34+
/**
35+
* Base infinite query for a track's root comment IDs. Keeps a live observer on
36+
* the comment-list query (which uses `gcTime: 0`, so the data is evicted the
37+
* moment no observer is mounted) and primes individual comment data into the
38+
* cache. Shared by `useTrackComments` and `usePrefetchTrackComments`.
39+
*/
40+
const useTrackCommentsQuery = (
3241
{
3342
trackId,
3443
sortMethod,
@@ -39,10 +48,9 @@ export const useTrackComments = (
3948
const { audiusSdk } = useQueryContext()
4049
const isMutating = useIsMutating()
4150
const queryClient = useQueryClient()
42-
const dispatch = useDispatch()
4351
const { data: currentUserId } = useCurrentUserId()
4452

45-
const queryRes = useInfiniteQuery({
53+
return useInfiniteQuery({
4654
initialPageParam: 0,
4755
getNextPageParam: (lastPage: ID[], pages) => {
4856
if (lastPage?.length < pageSize) return undefined
@@ -78,6 +86,31 @@ export const useTrackComments = (
7886
...options,
7987
enabled: isMutating === 0 && options?.enabled !== false && !!trackId
8088
})
89+
}
90+
91+
/**
92+
* Warms the track comment list as early as possible (e.g. on track screen
93+
* mount, in parallel with the track fetch) so the comment section renders with
94+
* data already in cache instead of starting its fetch only once it mounts.
95+
*
96+
* Uses the default `Top` sort and page size to match what the comment section
97+
* requests by default, ensuring a cache hit. Keeps a live observer mounted so
98+
* the `gcTime: 0` query isn't evicted before the comment section mounts.
99+
*/
100+
export const usePrefetchTrackComments = (trackId: ID | null | undefined) => {
101+
useTrackCommentsQuery(
102+
{ trackId: trackId as ID, sortMethod: CommentSortMethod.Top },
103+
{ enabled: !!trackId }
104+
)
105+
}
106+
107+
export const useTrackComments = (
108+
args: GetCommentsByTrackArgs,
109+
options?: QueryOptions
110+
) => {
111+
const dispatch = useDispatch()
112+
113+
const queryRes = useTrackCommentsQuery(args, options)
81114

82115
const { error, data: commentIds } = queryRes
83116

packages/common/src/api/tan-query/lineups/useTrackPageLineup.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,23 @@ export const useTrackPageLineup = (
226226
queryKey
227227
}
228228
}
229+
230+
/**
231+
* Warms the track page lineup ("more by", remixes, "you might also like") as
232+
* early as possible so the lineup renders from cache instead of starting its
233+
* fetch only once `TrackScreenLineup` mounts — which on mobile is gated behind
234+
* the screen-ready / nav-animation delay in `ScreenSecondaryContent`.
235+
*
236+
* Unlike comments, this query genuinely depends on the hero track and its
237+
* owner's handle (see the `enabled` guard in `useTrackPageLineup`), so it can't
238+
* fire from a bare trackId before the track resolves. But hoisting it above the
239+
* secondary content gate lets it fire the instant those deps are ready —
240+
* overlapping the push transition instead of waiting for it to finish. Keeps a
241+
* live observer so the warmed data is shared with `TrackScreenLineup`'s own
242+
* query.
243+
*/
244+
export const usePrefetchTrackPageLineup = (
245+
trackId: ID | null | undefined
246+
) => {
247+
useTrackPageLineup({ trackId })
248+
}

packages/mobile/src/screens/track-screen/TrackScreen.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { useRef } from 'react'
22

3-
import { useTrackByParams, useUser } from '@audius/common/api'
3+
import {
4+
useTrackByParams,
5+
usePrefetchTrackComments,
6+
usePrefetchTrackPageLineup,
7+
useUser
8+
} from '@audius/common/api'
49
import { Kind } from '@audius/common/models'
510
import { reachabilitySelectors } from '@audius/common/store'
611
import { makeStableUid } from '@audius/common/utils'
@@ -34,6 +39,22 @@ export const TrackScreen = () => {
3439
const { data: fetchedTrack } = useTrackByParams(restParams)
3540
const track = fetchedTrack ?? searchTrack
3641

42+
// Kick off the comments fetch as early as possible — on mount, in parallel
43+
// with the track/user fetch — so the comment section renders from cache
44+
// instead of starting its own fetch only once it mounts (gated behind the
45+
// user fetch and the secondary-content gate below). Uses the trackId from
46+
// route params when available so it can fire before the track resolves.
47+
const paramTrackId =
48+
'trackId' in restParams ? restParams.trackId : undefined
49+
const trackId = paramTrackId ?? track?.track_id
50+
usePrefetchTrackComments(track?.comments_disabled ? null : trackId)
51+
52+
// Warm the "more by / remixes / you might also like" lineup too. Unlike
53+
// comments it can't fire from the bare trackId (it needs the hero track +
54+
// owner handle), but hoisting it here lets it start as soon as those resolve
55+
// instead of waiting for the ScreenSecondaryContent screen-ready gate.
56+
usePrefetchTrackPageLineup(trackId)
57+
3758
const { data: user } = useUser(track?.owner_id)
3859

3960
if (!track || !user) {

0 commit comments

Comments
 (0)