From 0afb37bb7b50d6c28f0063ad0bdb7ece89be61ba Mon Sep 17 00:00:00 2001 From: paolomolo Date: Mon, 24 Nov 2025 13:42:33 +0000 Subject: [PATCH 1/5] fix(popular-feed): refactor to correctly update window and refetch - Update window correctly when refetching popular feed - Fix refetch logic for popular feed --- src/features/social/views/FeedList.tsx | 50 ++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/features/social/views/FeedList.tsx b/src/features/social/views/FeedList.tsx index 172d58843..6a16d10f3 100644 --- a/src/features/social/views/FeedList.tsx +++ b/src/features/social/views/FeedList.tsx @@ -111,13 +111,29 @@ export default function FeedList({ const params = new URLSearchParams(location.search); const fromUrl = (params.get("window") as '24h'|'7d'|'all' | null) || '24h'; if (fromUrl !== popularWindow) { + if (process.env.NODE_ENV === 'development') { + console.log('[Popular Feed] URL window changed from', popularWindow, 'to', fromUrl); + } setPopularWindow(fromUrl); if (sortBy === 'hot') { // Reset cached pages to avoid mixing windows queryClient.removeQueries({ queryKey: ["popular-posts"], exact: false }); + // Invalidate the new query to force refetch + queryClient.invalidateQueries({ queryKey: ["popular-posts", { limit: 10, window: fromUrl }] }); } } }, [location.search, sortBy, queryClient, popularWindow]); + + // Refetch popular posts when window changes (to ensure fresh data) + useEffect(() => { + if (sortBy === 'hot' && popularData && popularData.pages.length > 0) { + // Check if the current data matches the current window + // This is a safety check - React Query should handle this automatically via query key + if (process.env.NODE_ENV === 'development') { + console.log('[Popular Feed] Window changed, ensuring query is active for window:', popularWindow); + } + } + }, [popularWindow, sortBy, popularData]); // Helper to map a token object or websocket payload into a Post-like item const mapTokenCreatedToPost = useCallback((payload: any): PostDto => { @@ -359,6 +375,9 @@ export default function FeedList({ enabled: sortBy === "hot", queryKey: ["popular-posts", { limit: 10, window: popularWindow }], queryFn: async ({ pageParam = 1 }) => { + if (process.env.NODE_ENV === 'development') { + console.log('[Popular Feed] Fetching with window:', popularWindow, 'page:', pageParam); + } const response = await SuperheroApi.listPopularPosts({ window: popularWindow, page: pageParam as number, @@ -366,8 +385,10 @@ export default function FeedList({ }); if (process.env.NODE_ENV === 'development') { console.log('[Popular Feed] Query response:', { + window: popularWindow, pageParam, - response, + firstItemId: response?.items?.[0]?.id, + itemsCount: response?.items?.length, meta: response?.meta, }); } @@ -601,11 +622,21 @@ export default function FeedList({ }, [latestData]); const popularList = useMemo( - () => - popularData?.pages + () => { + const list = popularData?.pages ? ((popularData.pages as any[]) || []).flatMap((page: any) => page?.items ?? []) - : [], - [popularData] + : []; + if (process.env.NODE_ENV === 'development' && sortBy === 'hot') { + console.log('[Popular Feed] popularList computed:', { + window: popularWindow, + itemsCount: list.length, + firstItemId: list[0]?.id, + pagesCount: popularData?.pages?.length, + }); + } + return list; + }, + [popularData, popularWindow, sortBy] ); // Check if we have enough popular posts (at least 10) to fill the initial view @@ -879,13 +910,18 @@ export default function FeedList({ ); const handlePopularWindowChange = useCallback((w: '24h'|'7d'|'all') => { + if (process.env.NODE_ENV === 'development') { + console.log('[Popular Feed] Window changing from', popularWindow, 'to', w); + } setPopularWindow(w); if (sortBy === 'hot') { navigate(`/?sortBy=hot&window=${w}`); - // Reset pages for new window + // Reset pages for new window - this ensures React Query creates a fresh query queryClient.removeQueries({ queryKey: ["popular-posts"], exact: false }); + // Also invalidate to force refetch + queryClient.invalidateQueries({ queryKey: ["popular-posts", { limit: 10, window: w }] }); } - }, [navigate, sortBy, queryClient]); + }, [navigate, sortBy, queryClient, popularWindow]); const handleItemClick = useCallback( (idOrSlug: string) => { From 79dbabb2d9880fa623b0665211f37f8e3a4d9bdc Mon Sep 17 00:00:00 2001 From: paolomolo Date: Mon, 24 Nov 2025 23:49:14 +0100 Subject: [PATCH 2/5] fix(feed): resolve popularData temporal dead zone error - Move useEffect hook that references popularData to after its declaration - Fixes 'Cannot access popularData before initialization' runtime error - useEffect now correctly placed after popularData query declaration --- src/features/social/views/FeedList.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/features/social/views/FeedList.tsx b/src/features/social/views/FeedList.tsx index 6a16d10f3..50502d3d0 100644 --- a/src/features/social/views/FeedList.tsx +++ b/src/features/social/views/FeedList.tsx @@ -124,17 +124,6 @@ export default function FeedList({ } }, [location.search, sortBy, queryClient, popularWindow]); - // Refetch popular posts when window changes (to ensure fresh data) - useEffect(() => { - if (sortBy === 'hot' && popularData && popularData.pages.length > 0) { - // Check if the current data matches the current window - // This is a safety check - React Query should handle this automatically via query key - if (process.env.NODE_ENV === 'development') { - console.log('[Popular Feed] Window changed, ensuring query is active for window:', popularWindow); - } - } - }, [popularWindow, sortBy, popularData]); - // Helper to map a token object or websocket payload into a Post-like item const mapTokenCreatedToPost = useCallback((payload: any): PostDto => { const saleAddress: string = payload?.sale_address || payload?.address || ""; @@ -456,6 +445,17 @@ export default function FeedList({ refetchOnWindowFocus: true, // Refetch when window regains focus }); + // Refetch popular posts when window changes (to ensure fresh data) + useEffect(() => { + if (sortBy === 'hot' && popularData && popularData.pages.length > 0) { + // Check if the current data matches the current window + // This is a safety check - React Query should handle this automatically via query key + if (process.env.NODE_ENV === 'development') { + console.log('[Popular Feed] Window changed, ensuring query is active for window:', popularWindow); + } + } + }, [popularWindow, sortBy, popularData]); + // Track popular post IDs to filter them out from latest posts const popularPostIds = useMemo(() => { if (!popularData?.pages) return new Set(); From 08e2b9ff0432efa28d5d7c6540d91035851a4909 Mon Sep 17 00:00:00 2001 From: paolomolo Date: Mon, 24 Nov 2025 23:58:51 +0100 Subject: [PATCH 3/5] fix(feed): improve loading state detection for popular posts query - Add isFetching to popular posts query destructuring - Update initialLoading to check both isLoading and isFetching - Fixes stuck skeleton loaders when query is fetching but isLoading is false - Ensures loading state is shown whenever query is actively fetching data --- src/features/social/views/FeedList.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/features/social/views/FeedList.tsx b/src/features/social/views/FeedList.tsx index 50502d3d0..201d5f01a 100644 --- a/src/features/social/views/FeedList.tsx +++ b/src/features/social/views/FeedList.tsx @@ -355,6 +355,7 @@ export default function FeedList({ const { data: popularData, isLoading: popularLoading, + isFetching: popularFetching, error: popularError, fetchNextPage: fetchNextPopular, hasNextPage: hasMorePopular, @@ -945,8 +946,9 @@ export default function FeedList({ // Render helpers const renderEmptyState = () => { if (sortBy === "hot") { - // Only show loading if we don't have cached data - const initialLoading = popularLoading && (!popularData || (popularData as any)?.pages?.length === 0); + // Show loading if query is actively loading/fetching and we have no data + const hasNoData = !popularData || (popularData as any)?.pages?.length === 0; + const initialLoading = (popularLoading || popularFetching) && hasNoData; const err = popularError; if (err) { return { refetchPopular(); }} />; @@ -980,7 +982,7 @@ export default function FeedList({ const hasCachedData = sortBy !== "hot" && (hasQueryData || (hasCachedPostsData && hasCachedActivitiesData)); const initialLoading = sortBy === "hot" - ? (popularLoading && (!popularData || (popularData as any)?.pages?.length === 0)) + ? ((popularLoading || popularFetching) && (!popularData || (popularData as any)?.pages?.length === 0)) : (!hasCachedData && (latestLoading || activitiesLoading)); // Only show loading if no cached data and actually loading if (latestError) { return ; @@ -1146,7 +1148,7 @@ export default function FeedList({ const initialLoading = sortBy === "hot" - ? (popularLoading && (!popularData || (popularData as any)?.pages?.length === 0)) + ? ((popularLoading || popularFetching) && (!popularData || (popularData as any)?.pages?.length === 0)) : (!hasCachedDataForLatest && (latestLoading || activitiesLoading)); // Only show loading if no cached data and actually loading const [showLoadMore, setShowLoadMore] = useState(false); useEffect(() => { setShowLoadMore(false); }, [sortBy]); From d65434d7838c44faa916912cc19140b1ceaee440 Mon Sep 17 00:00:00 2001 From: paolomolo Date: Tue, 25 Nov 2025 00:09:01 +0100 Subject: [PATCH 4/5] fix(feed): prevent skeletons from showing when data exists - Fix renderEmptyState to check for data pages before showing skeletons - Add fallback to render popularList directly if filteredAndSortedList is empty - Improve popularList computation to handle different response structures - Add debug logging to diagnose data structure issues - Ensures skeletons only show during actual loading, not when data exists --- src/features/social/views/FeedList.tsx | 83 ++++++++++++++++++-------- 1 file changed, 58 insertions(+), 25 deletions(-) diff --git a/src/features/social/views/FeedList.tsx b/src/features/social/views/FeedList.tsx index 201d5f01a..d88d4d9ad 100644 --- a/src/features/social/views/FeedList.tsx +++ b/src/features/social/views/FeedList.tsx @@ -624,15 +624,26 @@ export default function FeedList({ const popularList = useMemo( () => { - const list = popularData?.pages - ? ((popularData.pages as any[]) || []).flatMap((page: any) => page?.items ?? []) - : []; + if (!popularData?.pages) return []; + + const list = ((popularData.pages as any[]) || []).flatMap((page: any) => { + // Handle both direct items array and PostApiResponse structure + if (Array.isArray(page)) { + return page; + } + return page?.items ?? []; + }); + if (process.env.NODE_ENV === 'development' && sortBy === 'hot') { console.log('[Popular Feed] popularList computed:', { window: popularWindow, itemsCount: list.length, firstItemId: list[0]?.id, pagesCount: popularData?.pages?.length, + firstPageStructure: popularData?.pages?.[0] ? Object.keys(popularData.pages[0]) : [], + firstPageHasItems: !!(popularData?.pages?.[0] as any)?.items, + firstPageItemsLength: (popularData?.pages?.[0] as any)?.items?.length || 0, + firstPageIsArray: Array.isArray(popularData?.pages?.[0]), }); } return list; @@ -946,29 +957,37 @@ export default function FeedList({ // Render helpers const renderEmptyState = () => { if (sortBy === "hot") { - // Show loading if query is actively loading/fetching and we have no data + // Check if we have data pages (even if items are empty, pages existing means query completed) + const hasDataPages = popularData && (popularData as any)?.pages?.length > 0; const hasNoData = !popularData || (popularData as any)?.pages?.length === 0; const initialLoading = (popularLoading || popularFetching) && hasNoData; const err = popularError; + if (err) { return { refetchPopular(); }} />; } - // Show skeleton loaders when there are no popular posts for the selected window - if (!err && filteredAndSortedList.length === 0 && !initialLoading) { - return ( -
- {Array.from({ length: 3 }, (_, i) => )} -
- ); + + // If we have data pages (query completed), don't show skeletons - let the feed render + // Even if items are empty, the query completed so we shouldn't show loading skeletons + if (hasDataPages) { + return null; } - if (initialLoading && filteredAndSortedList.length === 0) { - // Show skeleton loaders instead of loading text + + // Only show skeletons when actively loading and no data yet + if (initialLoading) { return (
{Array.from({ length: 3 }, (_, i) => )}
); } + + // If query completed but returned no data, show empty state (not loading skeletons) + // This handles the case where API returns empty results + if (!initialLoading && hasNoData && !popularLoading && !popularFetching) { + return null; // Let the feed handle empty state, or show a message + } + return null; } // Only show loading if we don't have cached data @@ -1356,21 +1375,35 @@ export default function FeedList({ {/* Hot: render popular posts (which seamlessly includes recent posts after popular posts are exhausted) */} {sortBy === "hot" && (popularData?.pages.length > 0 || latestDataForHot?.pages.length > 0) && ( <> - {/* Debug log removed to reduce console spam */} - {/* {process.env.NODE_ENV === 'development' && console.log('🔍 [DEBUG] Rendering hot feed:', { + {process.env.NODE_ENV === 'development' && console.log('🔍 [DEBUG] Rendering hot feed:', { filteredAndSortedListLength: filteredAndSortedList.length, + popularListLength: popularList.length, popularDataPages: popularData?.pages?.length || 0, latestDataForHotPages: latestDataForHot?.pages?.length || 0, - })} */} - {filteredAndSortedList.map((item) => ( - - ))} + firstPageItems: (popularData?.pages?.[0] as any)?.items?.length || 0, + })} + {filteredAndSortedList.length > 0 ? ( + filteredAndSortedList.map((item) => ( + + )) + ) : popularList.length > 0 ? ( + // Fallback: render popularList directly if filteredAndSortedList is empty + popularList.map((item) => ( + + )) + ) : null} )} From 8f0701e1fafdf6648fa3c5f8e1a817513299afac Mon Sep 17 00:00:00 2001 From: paolomolo Date: Tue, 25 Nov 2025 00:10:37 +0100 Subject: [PATCH 5/5] fix(feed): ensure content renders and skeletons only show when loading - Fix renderEmptyState to check for actual content before hiding skeletons - Always render feed content if popularList or filteredAndSortedList has items - Improve popularList extraction to handle different response structures - Filter out invalid items in popularList computation - Add comprehensive debug logging for data structure diagnosis - Skeletons now only show during actual loading, not when data exists --- src/features/social/views/FeedList.tsx | 78 +++++++++++++++----------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/src/features/social/views/FeedList.tsx b/src/features/social/views/FeedList.tsx index d88d4d9ad..568efffdc 100644 --- a/src/features/social/views/FeedList.tsx +++ b/src/features/social/views/FeedList.tsx @@ -624,14 +624,19 @@ export default function FeedList({ const popularList = useMemo( () => { - if (!popularData?.pages) return []; + if (!popularData?.pages || popularData.pages.length === 0) return []; const list = ((popularData.pages as any[]) || []).flatMap((page: any) => { // Handle both direct items array and PostApiResponse structure if (Array.isArray(page)) { - return page; + return page.filter(item => item && item.id); // Filter out invalid items } - return page?.items ?? []; + // PostApiResponse structure: { items: [...], meta: {...} } + const items = page?.items; + if (Array.isArray(items)) { + return items.filter(item => item && item.id); // Filter out invalid items + } + return []; }); if (process.env.NODE_ENV === 'development' && sortBy === 'hot') { @@ -644,6 +649,7 @@ export default function FeedList({ firstPageHasItems: !!(popularData?.pages?.[0] as any)?.items, firstPageItemsLength: (popularData?.pages?.[0] as any)?.items?.length || 0, firstPageIsArray: Array.isArray(popularData?.pages?.[0]), + firstPageRaw: JSON.stringify(popularData?.pages?.[0]).substring(0, 200), }); } return list; @@ -957,8 +963,9 @@ export default function FeedList({ // Render helpers const renderEmptyState = () => { if (sortBy === "hot") { - // Check if we have data pages (even if items are empty, pages existing means query completed) + // Check if we have actual content to show const hasDataPages = popularData && (popularData as any)?.pages?.length > 0; + const hasContent = popularList.length > 0 || filteredAndSortedList.length > 0; const hasNoData = !popularData || (popularData as any)?.pages?.length === 0; const initialLoading = (popularLoading || popularFetching) && hasNoData; const err = popularError; @@ -967,12 +974,17 @@ export default function FeedList({ return { refetchPopular(); }} />; } - // If we have data pages (query completed), don't show skeletons - let the feed render - // Even if items are empty, the query completed so we shouldn't show loading skeletons - if (hasDataPages) { + // If we have content to show, don't show skeletons - let the feed render + if (hasContent) { return null; } + // If we have data pages but no content, query completed but returned empty + // Don't show loading skeletons in this case + if (hasDataPages && !hasContent) { + return null; // Query completed but no items - let feed handle it + } + // Only show skeletons when actively loading and no data yet if (initialLoading) { return ( @@ -982,10 +994,13 @@ export default function FeedList({ ); } - // If query completed but returned no data, show empty state (not loading skeletons) - // This handles the case where API returns empty results - if (!initialLoading && hasNoData && !popularLoading && !popularFetching) { - return null; // Let the feed handle empty state, or show a message + // If not loading and no data, show empty skeletons (for empty state) + if (!initialLoading && hasNoData) { + return ( +
+ {Array.from({ length: 3 }, (_, i) => )} +
+ ); } return null; @@ -1373,7 +1388,7 @@ export default function FeedList({ {sortBy !== "hot" && (latestData?.pages.length > 0 || activityList.length > 0) && renderFeedItems} {/* Hot: render popular posts (which seamlessly includes recent posts after popular posts are exhausted) */} - {sortBy === "hot" && (popularData?.pages.length > 0 || latestDataForHot?.pages.length > 0) && ( + {sortBy === "hot" && ( <> {process.env.NODE_ENV === 'development' && console.log('🔍 [DEBUG] Rendering hot feed:', { filteredAndSortedListLength: filteredAndSortedList.length, @@ -1381,29 +1396,24 @@ export default function FeedList({ popularDataPages: popularData?.pages?.length || 0, latestDataForHotPages: latestDataForHot?.pages?.length || 0, firstPageItems: (popularData?.pages?.[0] as any)?.items?.length || 0, + popularLoading, + popularFetching, + hasContent: popularList.length > 0 || filteredAndSortedList.length > 0, })} - {filteredAndSortedList.length > 0 ? ( - filteredAndSortedList.map((item) => ( - - )) - ) : popularList.length > 0 ? ( - // Fallback: render popularList directly if filteredAndSortedList is empty - popularList.map((item) => ( - - )) - ) : null} + {/* Always try to render content if we have it */} + {(filteredAndSortedList.length > 0 || popularList.length > 0) && ( + <> + {(filteredAndSortedList.length > 0 ? filteredAndSortedList : popularList).map((item) => ( + + ))} + + )} )}