From ecb9e60b46b889b8d9b317a6569975c5cb332250 Mon Sep 17 00:00:00 2001 From: Gandalf Date: Mon, 5 Jan 2026 22:08:36 +0100 Subject: [PATCH] Fix false 404/empty states when API fails Previously, profile pages and post lists would show 404 or "Nothing more to load" when the underlying API calls failed (e.g., 503 errors during high load). This created a poor user experience since: - Valid users appeared as "not found" during temporary API outages - Post lists showed "Nothing more to load" when posts couldn't be fetched - Clients couldn't retry because they were given false empty states Changes: profile-layout.tsx: - Add isError/isPending states from useQuery for profile and global data - Show NoDataError component when API fails (allows retry) - Show loading state (null) while queries are pending - Only call notFound() when API confirms user doesn't exist - Show NoDataError for data integrity issues (missing required fields) list-of-posts.tsx: - Add isPending state from useInfiniteQuery - Show NoDataError component when API fails - Show loading state (null) while query is pending - Only show "Nothing more to load" when data.pages[0].length > 0 Fixes #789 --- .../layouts/user-profile/profile-layout.tsx | 32 ++++++++++++++++--- .../features/tags-pages/list-of-posts.tsx | 17 ++++++++-- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/apps/blog/features/layouts/user-profile/profile-layout.tsx b/apps/blog/features/layouts/user-profile/profile-layout.tsx index 40f578fb9..f5b613d1f 100644 --- a/apps/blog/features/layouts/user-profile/profile-layout.tsx +++ b/apps/blog/features/layouts/user-profile/profile-layout.tsx @@ -20,6 +20,7 @@ import { convertStringToBig } from '@ui/lib/helpers'; import { accountReputation, compareDates } from '@/blog/lib/utils'; import CustomError from '@/blog/components/custom-error'; +import NoDataError from '@/blog/components/no-data-error'; import { getAccountFull, getAccountReputations, getDynamicGlobalProperties } from '@transaction/lib/hive-api'; import ButtonsContainer from '@/blog/features/mute-follow/buttons-container'; @@ -54,7 +55,11 @@ const ProfileLayout = ({ children }: { children: ReactNode }) => { const explorerHost = env('EXPLORER_DOMAIN'); const userFromGDPRList = gdprUserList.includes(username); - const { data: profileData } = useQuery({ + const { + data: profileData, + isError: isProfileError, + isLoading: isProfilePending + } = useQuery({ queryKey: ['profileData', username], queryFn: () => getAccountFull(username), enabled: !!username @@ -76,19 +81,38 @@ const ProfileLayout = ({ children }: { children: ReactNode }) => { retry: false, refetchOnWindowFocus: false }); - const { data: dynamicGlobalData } = useQuery({ + const { + data: dynamicGlobalData, + isError: isDynamicGlobalError, + isLoading: isDynamicGlobalPending + } = useQuery({ queryKey: ['dynamicGlobalData'], queryFn: () => getDynamicGlobalProperties() }); + // Handle API errors - show error state with retry option + if (isProfileError || isDynamicGlobalError) { + return ; + } + + // Handle loading state - wait for data + if (isProfilePending || isDynamicGlobalPending) { + return null; + } + + // Handle user not found - API succeeded but user doesn't exist + if (!profileData) { + return notFound(); + } + + // Handle missing required profile fields (data integrity issue) if ( !dynamicGlobalData || - !profileData || !profileData.delegated_vesting_shares || !profileData.received_vesting_shares || !profileData.vesting_shares ) { - return notFound(); + return ; } const delegated_hive = convertToHP( diff --git a/apps/blog/features/tags-pages/list-of-posts.tsx b/apps/blog/features/tags-pages/list-of-posts.tsx index fd21265e1..967613581 100644 --- a/apps/blog/features/tags-pages/list-of-posts.tsx +++ b/apps/blog/features/tags-pages/list-of-posts.tsx @@ -10,6 +10,7 @@ import { DEFAULT_OBSERVER, DEFAULT_PREFERENCES, Preferences, SortTypes } from '@ import { useTranslation } from '@/blog/i18n/client'; import { Entry } from '@transaction/lib/extended-hive.chain'; import PostList from '../list-of-posts/posts-loader'; +import NoDataError from '@/blog/components/no-data-error'; import { isCommunity } from '@ui/lib/utils'; const SortedPagesPosts = ({ sort, tag = '' }: { sort: SortTypes; tag?: string }) => { @@ -30,7 +31,7 @@ const SortedPagesPosts = ({ sort, tag = '' }: { sort: SortTypes; tag?: string }) DEFAULT_PREFERENCES ); - const { data, isFetching, isFetchingNextPage, fetchNextPage, hasNextPage, isError } = useInfiniteQuery({ + const { data, isFetching, isFetchingNextPage, fetchNextPage, hasNextPage, isError, isLoading } = useInfiniteQuery({ queryKey: ['entriesInfinite', sort, tag], queryFn: async ({ pageParam }) => { const { author, permlink } = (pageParam as { author?: string; permlink?: string }) || {}; @@ -62,6 +63,16 @@ const SortedPagesPosts = ({ sort, tag = '' }: { sort: SortTypes; tag?: string }) // Calculate total posts to determine when to show prefetch trigger const totalPosts = data?.pages?.reduce((acc, page) => acc + (page?.length || 0), 0) || 0; + // Handle API error - show error state with retry option + if (isError) { + return ; + } + + // Handle initial loading state + if (isLoading) { + return null; + } + return ( <> {!data @@ -89,9 +100,9 @@ const SortedPagesPosts = ({ sort, tag = '' }: { sort: SortTypes; tag?: string })
Loading...
) : hasNextPage ? ( t('user_profile.load_newer') - ) : ( + ) : data?.pages?.[0] && data.pages[0].length > 0 ? ( t('user_profile.nothing_more_to_load') - )} + ) : null}
{isFetching && !isFetchingNextPage ? 'Background Updating...' : null}
-- GitLab