mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(board): prevent transient no threads state (#1151)
* fix(board): prevent transient no threads state * fix(board): scope raw thread fallback by sort * fix(board): keep flash table loading during feed sync
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { useAccount, type Comment, type CommunitiesPages, type Community } from '@bitsocial/bitsocial-react-hooks';
|
||||
import { useAccount, type Comment } from '@bitsocial/bitsocial-react-hooks';
|
||||
import accountsStore from '@bitsocial/bitsocial-react-hooks/dist/stores/accounts';
|
||||
import communitiesStore from '@bitsocial/bitsocial-react-hooks/dist/stores/communities';
|
||||
import communitiesPagesStore, { getCommunityFirstPageCid, getCommunityPages } from '@bitsocial/bitsocial-react-hooks/dist/stores/communities-pages';
|
||||
import communitiesPagesStore from '@bitsocial/bitsocial-react-hooks/dist/stores/communities-pages';
|
||||
import { getCommentCommunityAddress } from '../lib/utils/comment-utils';
|
||||
import { isCommentArchived } from '../lib/utils/comment-moderation-utils';
|
||||
import { getRawBoardThreadState } from '../lib/utils/raw-board-thread-state';
|
||||
import { useDirectories } from './use-directories';
|
||||
import { getBoardAddressKeys, isBoardAddressInScope } from './use-hidden-catalog-threads';
|
||||
|
||||
@@ -15,72 +16,12 @@ type UsePruneHiddenCatalogThreadsOptions = {
|
||||
sortType: 'active' | 'new';
|
||||
};
|
||||
|
||||
type RawBoardCatalogState = {
|
||||
isFullyLoaded: boolean;
|
||||
rootThreadCids: Set<string>;
|
||||
};
|
||||
|
||||
const EMPTY_RAW_BOARD_CATALOG_STATE: RawBoardCatalogState = {
|
||||
isFullyLoaded: false,
|
||||
rootThreadCids: new Set<string>(),
|
||||
};
|
||||
|
||||
const addRootThreadCids = (cids: Set<string>, comments: readonly Comment[] | undefined) => {
|
||||
for (const comment of comments || []) {
|
||||
if (comment?.cid && !comment.parentCid) {
|
||||
cids.add(comment.cid);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getRawBoardCatalogState = ({
|
||||
accountId,
|
||||
communitiesPages,
|
||||
community,
|
||||
sortType,
|
||||
}: {
|
||||
accountId: string | undefined;
|
||||
communitiesPages: CommunitiesPages;
|
||||
community: Community | undefined;
|
||||
sortType: 'active' | 'new';
|
||||
}): RawBoardCatalogState => {
|
||||
if (!community) {
|
||||
return EMPTY_RAW_BOARD_CATALOG_STATE;
|
||||
}
|
||||
|
||||
const rootThreadCids = new Set<string>();
|
||||
const preloadedSortPage = community.posts?.pages?.[sortType];
|
||||
addRootThreadCids(rootThreadCids, preloadedSortPage?.comments);
|
||||
|
||||
const firstPageCid = getCommunityFirstPageCid(community, sortType, 'posts');
|
||||
const pages = firstPageCid ? getCommunityPages(community, sortType, communitiesPages, 'posts', accountId) : [];
|
||||
for (const page of pages) {
|
||||
addRootThreadCids(rootThreadCids, page?.comments);
|
||||
}
|
||||
|
||||
if (pages.length > 0) {
|
||||
return {
|
||||
isFullyLoaded: !pages[pages.length - 1]?.nextCid,
|
||||
rootThreadCids,
|
||||
};
|
||||
}
|
||||
|
||||
const pageCids = community.posts?.pageCids || {};
|
||||
const hasPageCids = Object.keys(pageCids).length > 0;
|
||||
const preloadedPages = Object.values(community.posts?.pages || {}) as Array<{ comments?: Comment[]; nextCid?: string }>;
|
||||
const hasCompletePreloadedPage = !hasPageCids && preloadedPages.some((page) => Array.isArray(page?.comments)) && preloadedPages.every((page) => !page?.nextCid);
|
||||
|
||||
if (hasCompletePreloadedPage) {
|
||||
for (const page of preloadedPages) {
|
||||
addRootThreadCids(rootThreadCids, page?.comments);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isFullyLoaded: hasCompletePreloadedPage,
|
||||
rootThreadCids,
|
||||
};
|
||||
};
|
||||
const EMPTY_RAW_BOARD_THREAD_STATE = getRawBoardThreadState({
|
||||
accountId: undefined,
|
||||
communitiesPages: {},
|
||||
community: undefined,
|
||||
sortType: 'active',
|
||||
});
|
||||
|
||||
const usePruneHiddenCatalogThreads = ({ enabled, hiddenThreadCandidates, communityAddress, sortType }: UsePruneHiddenCatalogThreadsOptions) => {
|
||||
const account = useAccount();
|
||||
@@ -92,13 +33,13 @@ const usePruneHiddenCatalogThreads = ({ enabled, hiddenThreadCandidates, communi
|
||||
const rawBoardCatalogState = useMemo(
|
||||
() =>
|
||||
enabled
|
||||
? getRawBoardCatalogState({
|
||||
? getRawBoardThreadState({
|
||||
accountId: account?.id,
|
||||
communitiesPages,
|
||||
community,
|
||||
sortType,
|
||||
})
|
||||
: EMPTY_RAW_BOARD_CATALOG_STATE,
|
||||
: EMPTY_RAW_BOARD_THREAD_STATE,
|
||||
[account?.id, communitiesPages, community, enabled, sortType],
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { Comment, CommunitiesPages, Community } from '@bitsocial/bitsocial-react-hooks';
|
||||
import { getRawBoardThreadState } from '../raw-board-thread-state';
|
||||
|
||||
const rootThread = (cid: string): Comment =>
|
||||
({
|
||||
cid,
|
||||
postCid: cid,
|
||||
}) as Comment;
|
||||
|
||||
describe('getRawBoardThreadState', () => {
|
||||
it('keeps the preloaded fallback scoped to the requested sort type', () => {
|
||||
const community = {
|
||||
posts: {
|
||||
pageCids: {
|
||||
new: 'new-page-1',
|
||||
},
|
||||
pages: {
|
||||
active: {
|
||||
comments: [],
|
||||
},
|
||||
new: {
|
||||
comments: [rootThread('new-thread')],
|
||||
nextCid: 'new-page-2',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Community;
|
||||
|
||||
expect(
|
||||
getRawBoardThreadState({
|
||||
accountId: undefined,
|
||||
communitiesPages: {} as CommunitiesPages,
|
||||
community,
|
||||
sortType: 'active',
|
||||
}),
|
||||
).toMatchObject({
|
||||
isFullyLoaded: true,
|
||||
rootThreadCids: new Set<string>(),
|
||||
});
|
||||
});
|
||||
|
||||
it('treats an empty preloaded requested-sort page as a fully loaded empty board', () => {
|
||||
const community = {
|
||||
posts: {
|
||||
pages: {
|
||||
active: {
|
||||
comments: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Community;
|
||||
|
||||
expect(
|
||||
getRawBoardThreadState({
|
||||
accountId: undefined,
|
||||
communitiesPages: {} as CommunitiesPages,
|
||||
community,
|
||||
sortType: 'active',
|
||||
}).isFullyLoaded,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { Comment, CommunitiesPages, Community } from '@bitsocial/bitsocial-react-hooks';
|
||||
import { getCommunityFirstPageCid, getCommunityPages } from '@bitsocial/bitsocial-react-hooks/dist/stores/communities-pages';
|
||||
|
||||
export type RawBoardThreadState = {
|
||||
isFullyLoaded: boolean;
|
||||
rootThreadCids: Set<string>;
|
||||
};
|
||||
|
||||
const EMPTY_RAW_BOARD_THREAD_STATE: RawBoardThreadState = {
|
||||
isFullyLoaded: false,
|
||||
rootThreadCids: new Set<string>(),
|
||||
};
|
||||
|
||||
const addRootThreadCids = (cids: Set<string>, comments: readonly Comment[] | undefined) => {
|
||||
for (const comment of comments || []) {
|
||||
if (comment?.cid && !comment.parentCid) {
|
||||
cids.add(comment.cid);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getRawBoardThreadState = ({
|
||||
accountId,
|
||||
communitiesPages,
|
||||
community,
|
||||
sortType,
|
||||
}: {
|
||||
accountId: string | undefined;
|
||||
communitiesPages: CommunitiesPages;
|
||||
community: Community | undefined;
|
||||
sortType: 'active' | 'new';
|
||||
}): RawBoardThreadState => {
|
||||
if (!community) {
|
||||
return EMPTY_RAW_BOARD_THREAD_STATE;
|
||||
}
|
||||
|
||||
const rootThreadCids = new Set<string>();
|
||||
const preloadedSortPage = community.posts?.pages?.[sortType];
|
||||
addRootThreadCids(rootThreadCids, preloadedSortPage?.comments);
|
||||
|
||||
const firstPageCid = getCommunityFirstPageCid(community, sortType, 'posts');
|
||||
const pages = firstPageCid ? getCommunityPages(community, sortType, communitiesPages, 'posts', accountId) : [];
|
||||
for (const page of pages) {
|
||||
addRootThreadCids(rootThreadCids, page?.comments);
|
||||
}
|
||||
|
||||
if (pages.length > 0) {
|
||||
return {
|
||||
isFullyLoaded: !pages[pages.length - 1]?.nextCid,
|
||||
rootThreadCids,
|
||||
};
|
||||
}
|
||||
|
||||
const hasPageCid = Boolean(community.posts?.pageCids?.[sortType]);
|
||||
const preloadedPages = (preloadedSortPage ? [preloadedSortPage] : []) as Array<{ comments?: Comment[]; nextCid?: string }>;
|
||||
const hasCompletePreloadedPage = !hasPageCid && preloadedPages.some((page) => Array.isArray(page?.comments)) && preloadedPages.every((page) => !page?.nextCid);
|
||||
|
||||
if (hasCompletePreloadedPage) {
|
||||
for (const page of preloadedPages) {
|
||||
addRootThreadCids(rootThreadCids, page?.comments);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isFullyLoaded: hasCompletePreloadedPage,
|
||||
rootThreadCids,
|
||||
};
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import { createElement } from 'react';
|
||||
import { createRoot, type Root } from 'react-dom/client';
|
||||
import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import communitiesPagesStore from '@bitsocial/bitsocial-react-hooks/dist/stores/communities-pages';
|
||||
import Board, { type BoardProps } from '../board';
|
||||
import { clearStableLastVisitTimeFilterName, LAST_VISIT_STORAGE_KEY } from '../../../lib/utils/time-filter-utils';
|
||||
|
||||
@@ -35,6 +36,10 @@ type TestComment = {
|
||||
type TestCommunity = {
|
||||
error?: Error;
|
||||
nameResolved?: boolean;
|
||||
posts?: {
|
||||
pageCids?: Record<string, string>;
|
||||
pages?: Record<string, { comments?: TestComment[]; nextCid?: string }>;
|
||||
};
|
||||
shortAddress?: string;
|
||||
state?: string;
|
||||
title?: string;
|
||||
@@ -312,6 +317,19 @@ const flushEffects = async (count = 5) => {
|
||||
}
|
||||
};
|
||||
|
||||
const markRawBoardThreadsFullyLoaded = (comments: TestComment[] = []) => {
|
||||
testState.community = {
|
||||
...testState.community,
|
||||
posts: {
|
||||
pages: {
|
||||
active: {
|
||||
comments,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const renderBoard = async ({
|
||||
boardProps,
|
||||
initialEntry,
|
||||
@@ -393,6 +411,7 @@ describe('Board', () => {
|
||||
document.title = 'before';
|
||||
clearStableLastVisitTimeFilterName();
|
||||
localStorage.setItem(LAST_VISIT_STORAGE_KEY, String(Date.now()));
|
||||
communitiesPagesStore.setState({ communitiesPages: {}, comments: {} });
|
||||
Object.defineProperty(window, 'scrollTo', {
|
||||
configurable: true,
|
||||
value: vi.fn(),
|
||||
@@ -407,6 +426,7 @@ describe('Board', () => {
|
||||
afterEach(() => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
communitiesPagesStore.setState({ communitiesPages: {}, comments: {} });
|
||||
clearStableLastVisitTimeFilterName();
|
||||
localStorage.clear();
|
||||
});
|
||||
@@ -622,6 +642,7 @@ describe('Board', () => {
|
||||
shortAddress: 'flash-posting.bso',
|
||||
title: '/f/ - Flash',
|
||||
};
|
||||
markRawBoardThreadsFullyLoaded();
|
||||
|
||||
await renderBoard({ initialEntry: '/f', routePath: '/:boardIdentifier/*' });
|
||||
|
||||
@@ -655,6 +676,7 @@ describe('Board', () => {
|
||||
title: '/f/ - Flash',
|
||||
};
|
||||
testState.hasMore = true;
|
||||
markRawBoardThreadsFullyLoaded();
|
||||
|
||||
await renderBoard({ initialEntry: '/f', routePath: '/:boardIdentifier/*' });
|
||||
|
||||
@@ -988,10 +1010,28 @@ describe('Board', () => {
|
||||
state: 'succeeded',
|
||||
title: '/mu/ - Music',
|
||||
};
|
||||
markRawBoardThreadsFullyLoaded();
|
||||
|
||||
await renderBoard({ initialEntry: '/mu', routePath: '/:boardIdentifier/*' });
|
||||
|
||||
expect(container.textContent).toContain('no_threads');
|
||||
expect(container.querySelector('[data-testid="loading-ellipsis"]')).toBeNull();
|
||||
});
|
||||
|
||||
it('does not show no threads after board metadata loads but raw thread pages are still missing', async () => {
|
||||
testState.feedStateString = undefined;
|
||||
testState.feedState = 'succeeded';
|
||||
testState.hasMore = false;
|
||||
testState.community = {
|
||||
error: undefined,
|
||||
shortAddress: 'music-posting.eth',
|
||||
state: 'succeeded',
|
||||
title: '/mu/ - Music',
|
||||
};
|
||||
|
||||
await renderBoard({ initialEntry: '/mu', routePath: '/:boardIdentifier/*' });
|
||||
|
||||
expect(container.textContent).not.toContain('no_threads');
|
||||
expect(container.querySelector('[data-testid="loading-ellipsis"]')?.textContent).toBe('downloading_board');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { Link, useLocation, useNavigate, useNavigationType, useParams } from 'react-router-dom';
|
||||
import { Comment, useAccount, useAccountComments, useCommunity, useFeed } from '@bitsocial/bitsocial-react-hooks';
|
||||
import { useCommunityField } from '../../hooks/use-stable-community';
|
||||
import communitiesPagesStore from '@bitsocial/bitsocial-react-hooks/dist/stores/communities-pages';
|
||||
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import styles from './board.module.css';
|
||||
@@ -19,6 +20,7 @@ import usePostNumberStore from '../../stores/use-post-number-store';
|
||||
import { useBoardFeedPageSize } from '../../hooks/use-board-feed-page-size';
|
||||
import useExpandedTimeFilter from '../../hooks/use-expanded-time-filter';
|
||||
import useIsMobile from '../../hooks/use-is-mobile';
|
||||
import { useNowSeconds } from '../../hooks/use-now-seconds';
|
||||
import { useSuggestionFeedLoader } from '../../hooks/use-suggestion-feed-loader';
|
||||
import useTimeFilter from '../../hooks/use-time-filter';
|
||||
import { getPageSlice } from '../../lib/utils/board-feed-pagination';
|
||||
@@ -26,6 +28,7 @@ import { getPageFromFeedPath, isDirectoryBoard, normalizeMultiboardFeedPath, str
|
||||
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
|
||||
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
|
||||
import { getNonokoPendingAccountCommentIndex } from '../../lib/utils/post-options-utils';
|
||||
import { getRawBoardThreadState } from '../../lib/utils/raw-board-thread-state';
|
||||
import { getSearchWithTimeFilter, getTimeFilterSuggestion, type TimeFilterSuggestion } from '../../lib/utils/time-filter-utils';
|
||||
import { getPretextItemSizeFromElement, resolveFeedVirtualizationMode } from '../../lib/utils/pretext-height-estimates';
|
||||
import { isFlashDirectory, isFlashDirectoryCode } from '../../lib/flash-tags';
|
||||
@@ -45,6 +48,7 @@ const MONTH_IN_SECONDS = 30 * 24 * 60 * 60;
|
||||
const YEAR_IN_SECONDS = 365 * 24 * 60 * 60;
|
||||
// Keep the hook on its indexed fast path when this view should not inject local posts.
|
||||
const EMPTY_ACCOUNT_COMMENT_LOOKUP = { commentIndices: [-1] };
|
||||
const EMPTY_COMMUNITIES_PAGES = {};
|
||||
|
||||
/** Board feed always uses 'active' sort; catalog dropdown does not affect board ordering. */
|
||||
const BOARD_SORT_TYPE = 'active' as const;
|
||||
@@ -55,6 +59,7 @@ interface BoardFooterProps {
|
||||
feedState: string | undefined;
|
||||
combinedFeedLength: number;
|
||||
isSingleCommunityBoard: boolean;
|
||||
isRawBoardThreadStateFullyLoaded: boolean;
|
||||
isInSubscriptionsView: boolean;
|
||||
isInModView: boolean;
|
||||
currentTimeFilterName: string;
|
||||
@@ -78,6 +83,7 @@ const BoardFooter = ({
|
||||
feedState,
|
||||
combinedFeedLength,
|
||||
isSingleCommunityBoard,
|
||||
isRawBoardThreadStateFullyLoaded,
|
||||
isInSubscriptionsView,
|
||||
isInModView,
|
||||
currentTimeFilterName,
|
||||
@@ -96,7 +102,7 @@ const BoardFooter = ({
|
||||
const isLoadedCommunityState = communityState === 'succeeded' || communityState === 'ready';
|
||||
const isFeedSucceeded = feedState === 'succeeded';
|
||||
const isFeedFailed = feedState === 'failed';
|
||||
const canShowNoThreads = isSingleCommunityBoard ? isLoadedCommunityState && isFeedSucceeded : isFeedSucceeded && !hasMore;
|
||||
const canShowNoThreads = isSingleCommunityBoard ? isLoadedCommunityState && isFeedSucceeded && isRawBoardThreadStateFullyLoaded : isFeedSucceeded && !hasMore;
|
||||
const isEmptyFeedLoading = combinedFeedLength === 0 && !canShowNoThreads && (isSingleCommunityBoard ? communityState !== 'failed' : !isFeedFailed);
|
||||
const showFooterLoading = showLoadingEllipsis && (hasMore || isEmptyFeedLoading);
|
||||
|
||||
@@ -306,6 +312,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
|
||||
[communityAddress],
|
||||
);
|
||||
const { accountComments: recentAccountComments } = useAccountComments(accountCommentLookupOptions);
|
||||
const nowSeconds = useNowSeconds(recentAccountComments.length > 0);
|
||||
const nonokoPendingAccountCommentIndex = getNonokoPendingAccountCommentIndex(routerLocation.state);
|
||||
const nonokoPendingAccountCommentLookupOptions = useMemo(
|
||||
() =>
|
||||
@@ -353,7 +360,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
|
||||
return (
|
||||
!deleted &&
|
||||
!removed &&
|
||||
timestamp > Date.now() / 1000 - RECENT_ACCOUNT_COMMENT_WINDOW_SECONDS &&
|
||||
timestamp > nowSeconds - RECENT_ACCOUNT_COMMENT_WINDOW_SECONDS &&
|
||||
state === 'succeeded' &&
|
||||
cid &&
|
||||
cid === postCid &&
|
||||
@@ -361,7 +368,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
|
||||
!feedCids.has(cid)
|
||||
);
|
||||
}),
|
||||
[recentAccountComments, communityAddress, feedCids],
|
||||
[recentAccountComments, communityAddress, feedCids, nowSeconds],
|
||||
);
|
||||
const localAccountComments = useMemo(() => {
|
||||
if (!nonokoPendingAccountComment) return filteredComments;
|
||||
@@ -451,6 +458,20 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
|
||||
// useCommunityField only reads from store, doesn't trigger fetching
|
||||
const communityData = useCommunity(communityIdentifier ? { community: communityIdentifier } : undefined);
|
||||
const { error: communityError, state: communityState } = communityData || {};
|
||||
const communitiesPages = communitiesPagesStore((state) => (isMultiboardView ? EMPTY_COMMUNITIES_PAGES : state.communitiesPages));
|
||||
const rawBoardThreadState = useMemo(
|
||||
() =>
|
||||
isMultiboardView
|
||||
? undefined
|
||||
: getRawBoardThreadState({
|
||||
accountId: account?.id,
|
||||
communitiesPages,
|
||||
community: communityData,
|
||||
sortType: BOARD_SORT_TYPE,
|
||||
}),
|
||||
[account?.id, communitiesPages, communityData, isMultiboardView],
|
||||
);
|
||||
const isRawBoardThreadStateFullyLoaded = rawBoardThreadState?.isFullyLoaded ?? false;
|
||||
const title = isInAllView ? t('all') : isInSubscriptionsView ? t('subscriptions') : isInModView ? t('mod') : communityTitle;
|
||||
|
||||
// Memoize footer component to preserve identity across renders (Virtuoso optimization)
|
||||
@@ -466,6 +487,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
|
||||
feedState={feedState}
|
||||
combinedFeedLength={combinedFeed.length}
|
||||
isSingleCommunityBoard={!isInAllView && !isInSubscriptionsView && !isInModView}
|
||||
isRawBoardThreadStateFullyLoaded={isRawBoardThreadStateFullyLoaded}
|
||||
isInSubscriptionsView={isInSubscriptionsView}
|
||||
isInModView={isInModView}
|
||||
currentTimeFilterName={currentTimeFilterName}
|
||||
@@ -546,6 +568,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
|
||||
communityAddresses,
|
||||
hasMore,
|
||||
combinedFeed.length,
|
||||
isRawBoardThreadStateFullyLoaded,
|
||||
isInAllView,
|
||||
isInSubscriptionsView,
|
||||
isInModView,
|
||||
@@ -644,8 +667,8 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
|
||||
const displayFeed = effectiveInfiniteScroll ? combinedFeed : currentPageFeed;
|
||||
const isLoadedCommunityState = communityState === 'succeeded' || communityState === 'ready';
|
||||
const isFeedSucceeded = feedState === 'succeeded';
|
||||
const shouldShowFlashTableLoading =
|
||||
shouldUseFlashTable && displayFeed.length === 0 && !(isLoadedCommunityState && isFeedSucceeded) && communityState !== 'failed' && feedState !== 'failed';
|
||||
const canShowEmptyFlashTable = isLoadedCommunityState && isFeedSucceeded && isRawBoardThreadStateFullyLoaded;
|
||||
const shouldShowFlashTableLoading = shouldUseFlashTable && displayFeed.length === 0 && !canShowEmptyFlashTable && communityState !== 'failed' && feedState !== 'failed';
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user