From 8b2e638c50553d88b85beda8805f3842240b7c2a Mon Sep 17 00:00:00 2001 From: Dylan Audius Date: Thu, 16 Jul 2026 12:44:04 -0700 Subject: [PATCH] fix(explore): wrap trending genre pills and honor the genre from the URL The Trending Genres pills on mobile were pinned to one line by a horizontal ScrollView, hiding all but the first few genres. Wrap them instead, matching the web section. Picking a second genre also kept showing the first one. TrendingPageContent read the genre from redux in preference to the URL, so arriving from Explore at /trending?genre=Rock with "Electronic" still in redux from an earlier visit discarded the param and rewrote the URL back to the stale genre. Treat the URL as the source of truth on mount and fall back to redux only when no genre param is present, so the last-used filter is still remembered when landing on a bare /trending. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../components/TrendingGenres.tsx | 43 +++++++------------ .../desktop/TrendingPageContent.tsx | 23 ++++++++-- .../components/mobile/TrendingPageContent.tsx | 38 +++++++++++++--- 3 files changed, 66 insertions(+), 38 deletions(-) diff --git a/packages/mobile/src/screens/explore-screen/components/TrendingGenres.tsx b/packages/mobile/src/screens/explore-screen/components/TrendingGenres.tsx index e047cba5f1e..ddcb4ebc08c 100644 --- a/packages/mobile/src/screens/explore-screen/components/TrendingGenres.tsx +++ b/packages/mobile/src/screens/explore-screen/components/TrendingGenres.tsx @@ -4,7 +4,6 @@ import { usePopularGenres } from '@audius/common/api' import { exploreMessages as messages } from '@audius/common/messages' import { trendingPageActions } from '@audius/common/store' import { toTrendingGenreValue } from '@audius/common/utils' -import { ScrollView } from 'react-native' import { useDispatch } from 'react-redux' import { Flex, SelectablePill, Skeleton } from '@audius/harmony-native' @@ -53,33 +52,21 @@ export const TrendingGenres = () => { return ( - - - {showSkeleton - ? Array.from({ length: SKELETON_COUNT }).map((_, i) => ( - - )) - : topGenres.map((genre) => ( - handleGenrePress(genre.value)} - /> - ))} - - + + {showSkeleton + ? Array.from({ length: SKELETON_COUNT }).map((_, i) => ( + + )) + : topGenres.map((genre) => ( + handleGenrePress(genre.value)} + /> + ))} + ) diff --git a/packages/web/src/pages/trending-page/components/desktop/TrendingPageContent.tsx b/packages/web/src/pages/trending-page/components/desktop/TrendingPageContent.tsx index b36945235fe..dd93e2df529 100644 --- a/packages/web/src/pages/trending-page/components/desktop/TrendingPageContent.tsx +++ b/packages/web/src/pages/trending-page/components/desktop/TrendingPageContent.tsx @@ -159,12 +159,20 @@ const TrendingPageContent = ({ containerRef }: TrendingPageContentProps) => { [dispatch] ) + // A `?genre=` in the URL is the user's intent for this visit and must win + // over whatever genre an earlier trending visit left behind in redux — + // otherwise arriving from Explore with `?genre=Jazz` silently keeps showing + // the previous genre. With no genre in the URL, redux still wins so the last + // filter is remembered (and gets written back to the URL by the sync effect + // below). + const genreFromUrlOnMount = useRef(parseUrlParams().genre) + useEffect(() => { const { genre, timeRange } = parseUrlParams() - if (trendingGenre) { - updateGenreUrlParam(trendingGenre, replaceRouteCallback) - } else if (isValidGenre(genre)) { - dispatch(trendingPageActions.setTrendingGenre(genre as any)) + if (isValidGenre(genre)) { + dispatch( + trendingPageActions.setTrendingGenre(toTrendingGenreValue(genre)) + ) } if (isValidTimeRange(timeRange)) { dispatch(trendingPageActions.setTrendingTimeRange(timeRange as TimeRange)) @@ -177,6 +185,13 @@ const TrendingPageContent = ({ containerRef }: TrendingPageContentProps) => { }, [trendingTimeRange, replaceRouteCallback]) useEffect(() => { + // Skip the first run when the URL already carries the genre: redux has not + // caught up with the effect above yet, and writing the stale value back + // would clobber the incoming param. + if (isValidGenre(genreFromUrlOnMount.current)) { + genreFromUrlOnMount.current = null + return + } updateGenreUrlParam(trendingGenre, replaceRouteCallback) }, [trendingGenre, replaceRouteCallback]) diff --git a/packages/web/src/pages/trending-page/components/mobile/TrendingPageContent.tsx b/packages/web/src/pages/trending-page/components/mobile/TrendingPageContent.tsx index 50c14d39ad3..39f7e2197f3 100644 --- a/packages/web/src/pages/trending-page/components/mobile/TrendingPageContent.tsx +++ b/packages/web/src/pages/trending-page/components/mobile/TrendingPageContent.tsx @@ -1,4 +1,11 @@ -import { useCallback, useContext, useEffect, useMemo, useState } from 'react' +import { + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState +} from 'react' import { getTrendingQueryKey, @@ -13,7 +20,11 @@ import { trendingPageActions, trendingPageSelectors } from '@audius/common/store' -import { route, toTrendingGenre } from '@audius/common/utils' +import { + route, + toTrendingGenre, + toTrendingGenreValue +} from '@audius/common/utils' import { FilterButton, Flex, @@ -136,12 +147,20 @@ const TrendingPageMobileContent = ({ dispatch(pushRoute(TRENDING_GENRES_ROUTE)) }, [dispatch]) + // A `?genre=` in the URL is the user's intent for this visit and must win + // over whatever genre an earlier trending visit left behind in redux — + // otherwise arriving from Explore with `?genre=Jazz` silently keeps showing + // the previous genre. With no genre in the URL, redux still wins so the last + // filter is remembered (and gets written back to the URL by the sync effect + // below). + const genreFromUrlOnMount = useRef(parseUrlParams().genre) + useEffect(() => { const { genre, timeRange } = parseUrlParams() - if (trendingGenre) { - updateGenreUrlParam(trendingGenre, replaceRouteCallback) - } else if (isValidGenre(genre)) { - dispatch(trendingPageActions.setTrendingGenre(genre as any)) + if (isValidGenre(genre)) { + dispatch( + trendingPageActions.setTrendingGenre(toTrendingGenreValue(genre)) + ) } if (isValidTimeRange(timeRange)) { dispatch(trendingPageActions.setTrendingTimeRange(timeRange as TimeRange)) @@ -152,6 +171,13 @@ const TrendingPageMobileContent = ({ updateTimeRangeUrlParam(trendingTimeRange, replaceRouteCallback) }, [trendingTimeRange, replaceRouteCallback]) useEffect(() => { + // Skip the first run when the URL already carries the genre: redux has not + // caught up with the effect above yet, and writing the stale value back + // would clobber the incoming param. + if (isValidGenre(genreFromUrlOnMount.current)) { + genreFromUrlOnMount.current = null + return + } updateGenreUrlParam(trendingGenre, replaceRouteCallback) }, [trendingGenre, replaceRouteCallback])