diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx index ca508e26..6e678d32 100644 --- a/src/components/post-desktop/post-desktop.tsx +++ b/src/components/post-desktop/post-desktop.tsx @@ -43,6 +43,8 @@ import usePostNumberStore from '../../stores/use-post-number-store'; import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils'; import { usePublishCommentModeration } from '@plebbit/plebbit-react-hooks'; import useQuotedByMap from '../../hooks/use-quoted-by-map'; +import useProgressiveRender from '../../hooks/use-progressive-render'; +import { REPLIES_PER_PAGE } from '../../lib/constants'; const { addChallenge } = useChallengesStore.getState(); @@ -674,6 +676,7 @@ const PostDesktop = ({ comment: post, sortType: 'old', flat: true, + repliesPerPage: REPLIES_PER_PAGE, accountComments: { newerThan: Infinity, append: true }, }); const { replies, hasMore, loadMore } = repliesResult; @@ -737,6 +740,13 @@ const PostDesktop = ({ const quotedByMap = useQuotedByMap(filteredReplies); + const visibleReplies = useProgressiveRender(filteredReplies, { + batchSize: 50, + intervalMs: 100, + resetKey: cid, + disabled: hasMore || !!targetReplyCid || !showAllReplies, + }); + // Virtuoso scroll position management for infinite replies const virtuosoRef = useRef(null); const virtuosoStateKey = `replies-desktop-${cid}`; @@ -888,7 +898,7 @@ const PostDesktop = ({ !isInPendingPostView && showReplies && !hasMore && - filteredReplies.map((reply, index) => ( + visibleReplies.map((reply, index) => (
(null); const virtuosoStateKey = `replies-mobile-${cid}`; @@ -670,7 +680,7 @@ const PostMobile = ({ !isInPendingPostView && showReplies && !hasMore && - filteredReplies.map((reply, index) => ( + visibleReplies.map((reply, index) => (
diff --git a/src/hooks/use-progressive-render.ts b/src/hooks/use-progressive-render.ts new file mode 100644 index 00000000..8f47185f --- /dev/null +++ b/src/hooks/use-progressive-render.ts @@ -0,0 +1,62 @@ +import { startTransition, useEffect, useState } from 'react'; + +interface UseProgressiveRenderOptions { + batchSize?: number; + intervalMs?: number; + resetKey?: string; + disabled?: boolean; +} + +const DEFAULT_BATCH_SIZE = 50; +const DEFAULT_INTERVAL_MS = 100; + +/** + * Progressively reveals items in batches via startTransition to avoid blocking the UI + * when mounting many heavy components (e.g. 500 reply components). + * When disabled or once fully caught up, returns the full array immediately. + */ +const useProgressiveRender = (items: T[], options: UseProgressiveRenderOptions = {}): T[] => { + const { batchSize = DEFAULT_BATCH_SIZE, intervalMs = DEFAULT_INTERVAL_MS, resetKey, disabled = false } = options; + + const [progressState, setProgressState] = useState<{ key: string | undefined; visibleCount: number }>({ + key: resetKey, + visibleCount: batchSize, + }); + const isResetKeyChanged = progressState.key !== resetKey; + const visibleCount = isResetKeyChanged ? batchSize : progressState.visibleCount; + + useEffect(() => { + if (disabled || items.length <= batchSize) { + return; + } + + const current = visibleCount >= items.length ? items.length : visibleCount; + if (current >= items.length) { + return; + } + + const next = Math.min(current + batchSize, items.length); + const timer = window.setTimeout(() => { + startTransition(() => { + setProgressState({ + key: resetKey, + visibleCount: next, + }); + }); + }, intervalMs); + + return () => window.clearTimeout(timer); + }, [items.length, visibleCount, batchSize, intervalMs, disabled, resetKey]); + + if (disabled || items.length <= batchSize) { + return items; + } + + if (visibleCount >= items.length) { + return items; + } + + return items.slice(0, visibleCount); +}; + +export default useProgressiveRender; diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 00000000..625871e8 --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1,2 @@ +/** Max replies loaded per page from useReplies. Threads with > this use Virtuoso. */ +export const REPLIES_PER_PAGE = 500; diff --git a/src/views/post/post.module.css b/src/views/post/post.module.css index fdd0d19d..5bcf3646 100644 --- a/src/views/post/post.module.css +++ b/src/views/post/post.module.css @@ -456,6 +456,8 @@ .replyDesktop { padding-top: 4px; + content-visibility: auto; + contain-intrinsic-size: auto 120px; } .replyDesktop .sideArrows { @@ -490,6 +492,8 @@ .replyMobile { padding-top: 7px; + content-visibility: auto; + contain-intrinsic-size: auto 120px; } .replyMobile .replyContainer {