diff --git a/.changeset/prefetch-track-comments.md b/.changeset/prefetch-track-comments.md new file mode 100644 index 00000000000..4b22872d60b --- /dev/null +++ b/.changeset/prefetch-track-comments.md @@ -0,0 +1,5 @@ +--- +'@audius/common': patch +--- + +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. diff --git a/packages/common/src/api/tan-query/comments/useTrackComments.ts b/packages/common/src/api/tan-query/comments/useTrackComments.ts index a6872592c7d..593b2a8d63b 100644 --- a/packages/common/src/api/tan-query/comments/useTrackComments.ts +++ b/packages/common/src/api/tan-query/comments/useTrackComments.ts @@ -1,6 +1,9 @@ import { useEffect } from 'react' -import { Id } from '@audius/sdk' +import { + GetTrackCommentsSortMethodEnum as CommentSortMethod, + Id +} from '@audius/sdk' import { useInfiniteQuery, useIsMutating, @@ -28,7 +31,13 @@ export type GetCommentsByTrackArgs = { pageSize?: number } -export const useTrackComments = ( +/** + * Base infinite query for a track's root comment IDs. Keeps a live observer on + * the comment-list query (which uses `gcTime: 0`, so the data is evicted the + * moment no observer is mounted) and primes individual comment data into the + * cache. Shared by `useTrackComments` and `usePrefetchTrackComments`. + */ +const useTrackCommentsQuery = ( { trackId, sortMethod, @@ -39,10 +48,9 @@ export const useTrackComments = ( const { audiusSdk } = useQueryContext() const isMutating = useIsMutating() const queryClient = useQueryClient() - const dispatch = useDispatch() const { data: currentUserId } = useCurrentUserId() - const queryRes = useInfiniteQuery({ + return useInfiniteQuery({ initialPageParam: 0, getNextPageParam: (lastPage: ID[], pages) => { if (lastPage?.length < pageSize) return undefined @@ -78,6 +86,31 @@ export const useTrackComments = ( ...options, enabled: isMutating === 0 && options?.enabled !== false && !!trackId }) +} + +/** + * Warms the track comment list as early as possible (e.g. on track screen + * mount, in parallel with the track fetch) so the comment section renders with + * data already in cache instead of starting its fetch only once it mounts. + * + * Uses the default `Top` sort and page size to match what the comment section + * requests by default, ensuring a cache hit. Keeps a live observer mounted so + * the `gcTime: 0` query isn't evicted before the comment section mounts. + */ +export const usePrefetchTrackComments = (trackId: ID | null | undefined) => { + useTrackCommentsQuery( + { trackId: trackId as ID, sortMethod: CommentSortMethod.Top }, + { enabled: !!trackId } + ) +} + +export const useTrackComments = ( + args: GetCommentsByTrackArgs, + options?: QueryOptions +) => { + const dispatch = useDispatch() + + const queryRes = useTrackCommentsQuery(args, options) const { error, data: commentIds } = queryRes diff --git a/packages/common/src/api/tan-query/lineups/useTrackPageLineup.ts b/packages/common/src/api/tan-query/lineups/useTrackPageLineup.ts index 850ec4d231b..5cd89c0f5b2 100644 --- a/packages/common/src/api/tan-query/lineups/useTrackPageLineup.ts +++ b/packages/common/src/api/tan-query/lineups/useTrackPageLineup.ts @@ -226,3 +226,23 @@ export const useTrackPageLineup = ( queryKey } } + +/** + * Warms the track page lineup ("more by", remixes, "you might also like") as + * early as possible so the lineup renders from cache instead of starting its + * fetch only once `TrackScreenLineup` mounts — which on mobile is gated behind + * the screen-ready / nav-animation delay in `ScreenSecondaryContent`. + * + * Unlike comments, this query genuinely depends on the hero track and its + * owner's handle (see the `enabled` guard in `useTrackPageLineup`), so it can't + * fire from a bare trackId before the track resolves. But hoisting it above the + * secondary content gate lets it fire the instant those deps are ready — + * overlapping the push transition instead of waiting for it to finish. Keeps a + * live observer so the warmed data is shared with `TrackScreenLineup`'s own + * query. + */ +export const usePrefetchTrackPageLineup = ( + trackId: ID | null | undefined +) => { + useTrackPageLineup({ trackId }) +} diff --git a/packages/mobile/src/screens/track-screen/TrackScreen.tsx b/packages/mobile/src/screens/track-screen/TrackScreen.tsx index 39476946c84..e49e5ef6f5b 100644 --- a/packages/mobile/src/screens/track-screen/TrackScreen.tsx +++ b/packages/mobile/src/screens/track-screen/TrackScreen.tsx @@ -1,6 +1,11 @@ import { useRef } from 'react' -import { useTrackByParams, useUser } from '@audius/common/api' +import { + useTrackByParams, + usePrefetchTrackComments, + usePrefetchTrackPageLineup, + useUser +} from '@audius/common/api' import { Kind } from '@audius/common/models' import { reachabilitySelectors } from '@audius/common/store' import { makeStableUid } from '@audius/common/utils' @@ -34,6 +39,22 @@ export const TrackScreen = () => { const { data: fetchedTrack } = useTrackByParams(restParams) const track = fetchedTrack ?? searchTrack + // Kick off the comments fetch as early as possible — on mount, in parallel + // with the track/user fetch — so the comment section renders from cache + // instead of starting its own fetch only once it mounts (gated behind the + // user fetch and the secondary-content gate below). Uses the trackId from + // route params when available so it can fire before the track resolves. + const paramTrackId = + 'trackId' in restParams ? restParams.trackId : undefined + const trackId = paramTrackId ?? track?.track_id + usePrefetchTrackComments(track?.comments_disabled ? null : trackId) + + // Warm the "more by / remixes / you might also like" lineup too. Unlike + // comments it can't fire from the bare trackId (it needs the hero track + + // owner handle), but hoisting it here lets it start as soon as those resolve + // instead of waiting for the ScreenSecondaryContent screen-ready gate. + usePrefetchTrackPageLineup(trackId) + const { data: user } = useUser(track?.owner_id) if (!track || !user) {