Skip to content

Commit cc06991

Browse files
committed
refactor(store): migrate RecentSongs to Context-based Zustand and fix pagination
- Migrate singleton Zustand store to React Context to allow synchronous SSR hydration and prevent initial render flashes. - Fix pagination desynchronization by incrementing the page state only after a successful network response. - Remove redundant ID-based concurrency checks, relying strictly on AbortController for deterministic network cancellation. - Delete implicit `useRecentSongsCategoriesLoader` side-effect hook in favor of explicit component-level data fetching. Fixes #87
1 parent f9a1b33 commit cc06991

5 files changed

Lines changed: 190 additions & 175 deletions

File tree

apps/frontend/src/app/(content)/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ async function fetchRecentSongs() {
99
try {
1010
const response = await axiosInstance.get<PageDto<SongPreviewDto>>('/song', {
1111
params: {
12-
page: 1, // TODO: fix constants
13-
limit: 11, // TODO: change 'limit' parameter to 'skip' and load 12 songs initially, then load 8 more songs on each pagination
12+
page: 1,
13+
limit: 12,
1414
sort: 'recent',
1515
order: 'desc',
1616
},

apps/frontend/src/modules/browse/components/HomePageComponent.tsx

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,15 @@ import { WelcomeBanner } from '../WelcomeBanner';
1919

2020
import { CategoryButtonGroup } from './client/CategoryButton';
2121
import { useFeaturedSongsProvider } from './client/context/FeaturedSongs.context';
22-
import {
23-
useRecentSongsProvider,
24-
useRecentSongsPageLoader,
25-
useRecentSongsCategoriesLoader,
26-
} from './client/context/RecentSongs.context';
22+
import { useRecentSongsProvider } from './client/context/RecentSongs.context';
2723
import LoadMoreButton from './client/LoadMoreButton';
2824
import { TimespanButtonGroup } from './client/TimespanButton';
2925
import SongCard from './SongCard';
3026
import SongCardGroup from './SongCardGroup';
3127

3228
export const HomePageComponent = () => {
33-
// Initialize sync hooks for proper effect handling
34-
useRecentSongsPageLoader();
35-
useRecentSongsCategoriesLoader();
36-
3729
const { featuredSongsPage, timespan } = useFeaturedSongsProvider();
38-
const { recentSongs, increasePageRecent, hasMore } = useRecentSongsProvider();
30+
const { recentItems, increasePageRecent, hasMore } = useRecentSongsProvider();
3931
return (
4032
<>
4133
{/* Welcome banner/Hero */}
@@ -87,14 +79,17 @@ export const HomePageComponent = () => {
8779
</div>
8880
<div className='h-6' />
8981
<SongCardGroup data-test='recent-songs'>
90-
{recentSongs.map((song, i) =>
91-
// TODO: currently null = skeleton, undefined = ad slot. There must be a more robust system to indicate this.
92-
song === undefined ? (
93-
<SongCardAdSlot key={i} />
94-
) : (
95-
<SongCard key={i} song={song} />
96-
),
97-
)}
82+
{recentItems.map((item, i) => {
83+
if (item.type === 'ad') {
84+
return <SongCardAdSlot key={i} />;
85+
}
86+
87+
if (item.type === 'loading') {
88+
return <SongCard key={i} song={null} />;
89+
}
90+
91+
return <SongCard key={i} song={item.data} />;
92+
})}
9893
</SongCardGroup>
9994
<div className='flex flex-col w-full justify-between items-center mt-4'>
10095
{hasMore ? (

apps/frontend/src/modules/browse/components/client/CategoryButton.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
'use client';
2+
import { useEffect } from 'react';
3+
24
import { UPLOAD_CONSTANTS } from '@nbw/config';
35
import type { CategoryType } from '@nbw/database';
46
import {
@@ -20,8 +22,15 @@ type CategoryButtonProps = {
2022
};
2123

2224
export const CategoryButtonGroup = () => {
23-
const { categories, setSelectedCategory, selectedCategory } =
25+
const { categories, fetchCategories, setSelectedCategory, selectedCategory } =
2426
useRecentSongsProvider();
27+
const categoryCount = Object.keys(categories).length;
28+
29+
useEffect(() => {
30+
if (categoryCount === 0) {
31+
void fetchCategories();
32+
}
33+
}, [categoryCount, fetchCategories]);
2534

2635
return (
2736
<Carousel

apps/frontend/src/modules/browse/components/client/context/HomePage.context.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,18 @@ import {
88
} from './FeaturedSongs.context';
99
import {
1010
RecentSongsProvider,
11-
useRecentSongsStore,
11+
useRecentSongsProvider,
1212
} from './RecentSongs.context';
1313

14-
/**
15-
* Composed hook that provides access to both FeaturedSongs and RecentSongs stores
16-
*/
1714
export function useHomePageStore() {
1815
const featuredSongs = useFeaturedSongsStore();
19-
const recentSongs = useRecentSongsStore();
16+
const recentSongs = useRecentSongsProvider();
2017

2118
return {
2219
featuredSongs,
2320
recentSongs,
2421
};
2522
}
26-
2723
export function HomePageProvider({
2824
children,
2925
initialRecentSongs,
@@ -41,8 +37,3 @@ export function HomePageProvider({
4137
</RecentSongsProvider>
4238
);
4339
}
44-
45-
// Legacy hook name for backward compatibility
46-
export function useHomePageProvider() {
47-
return useHomePageStore();
48-
}

0 commit comments

Comments
 (0)