mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
perf(replies): progressive render, 500 replies/page, content-visibility
Set repliesPerPage to 500 to avoid Virtuoso pagination sort drift. Add useProgressiveRender with startTransition batching and CSS content-visibility for long reply lists.
This commit is contained in:
@@ -43,6 +43,8 @@ import usePostNumberStore from '../../stores/use-post-number-store';
|
|||||||
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
||||||
import { usePublishCommentModeration } from '@plebbit/plebbit-react-hooks';
|
import { usePublishCommentModeration } from '@plebbit/plebbit-react-hooks';
|
||||||
import useQuotedByMap from '../../hooks/use-quoted-by-map';
|
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();
|
const { addChallenge } = useChallengesStore.getState();
|
||||||
|
|
||||||
@@ -674,6 +676,7 @@ const PostDesktop = ({
|
|||||||
comment: post,
|
comment: post,
|
||||||
sortType: 'old',
|
sortType: 'old',
|
||||||
flat: true,
|
flat: true,
|
||||||
|
repliesPerPage: REPLIES_PER_PAGE,
|
||||||
accountComments: { newerThan: Infinity, append: true },
|
accountComments: { newerThan: Infinity, append: true },
|
||||||
});
|
});
|
||||||
const { replies, hasMore, loadMore } = repliesResult;
|
const { replies, hasMore, loadMore } = repliesResult;
|
||||||
@@ -737,6 +740,13 @@ const PostDesktop = ({
|
|||||||
|
|
||||||
const quotedByMap = useQuotedByMap(filteredReplies);
|
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
|
// Virtuoso scroll position management for infinite replies
|
||||||
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
|
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
|
||||||
const virtuosoStateKey = `replies-desktop-${cid}`;
|
const virtuosoStateKey = `replies-desktop-${cid}`;
|
||||||
@@ -888,7 +898,7 @@ const PostDesktop = ({
|
|||||||
!isInPendingPostView &&
|
!isInPendingPostView &&
|
||||||
showReplies &&
|
showReplies &&
|
||||||
!hasMore &&
|
!hasMore &&
|
||||||
filteredReplies.map((reply, index) => (
|
visibleReplies.map((reply, index) => (
|
||||||
<div key={index} className={styles.replyContainer}>
|
<div key={index} className={styles.replyContainer}>
|
||||||
<Reply
|
<Reply
|
||||||
reply={reply}
|
reply={reply}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ import useFeedResetStore from '../../stores/use-feed-reset-store';
|
|||||||
import usePostNumberStore from '../../stores/use-post-number-store';
|
import usePostNumberStore from '../../stores/use-post-number-store';
|
||||||
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
||||||
import useQuotedByMap from '../../hooks/use-quoted-by-map';
|
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();
|
const { addChallenge } = useChallengesStore.getState();
|
||||||
|
|
||||||
@@ -497,6 +499,7 @@ const PostMobile = ({
|
|||||||
comment: post,
|
comment: post,
|
||||||
sortType: 'old',
|
sortType: 'old',
|
||||||
flat: true,
|
flat: true,
|
||||||
|
repliesPerPage: REPLIES_PER_PAGE,
|
||||||
accountComments: { newerThan: Infinity, append: true },
|
accountComments: { newerThan: Infinity, append: true },
|
||||||
});
|
});
|
||||||
const { replies, hasMore, loadMore } = repliesResult;
|
const { replies, hasMore, loadMore } = repliesResult;
|
||||||
@@ -537,6 +540,13 @@ const PostMobile = ({
|
|||||||
|
|
||||||
const quotedByMap = useQuotedByMap(filteredReplies);
|
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
|
// Virtuoso scroll position management for infinite replies
|
||||||
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
|
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
|
||||||
const virtuosoStateKey = `replies-mobile-${cid}`;
|
const virtuosoStateKey = `replies-mobile-${cid}`;
|
||||||
@@ -670,7 +680,7 @@ const PostMobile = ({
|
|||||||
!isInPendingPostView &&
|
!isInPendingPostView &&
|
||||||
showReplies &&
|
showReplies &&
|
||||||
!hasMore &&
|
!hasMore &&
|
||||||
filteredReplies.map((reply, index) => (
|
visibleReplies.map((reply, index) => (
|
||||||
<div key={index} className={styles.replyContainer}>
|
<div key={index} className={styles.replyContainer}>
|
||||||
<Reply postReplyCount={replyCount} reply={reply} roles={roles} threadNumber={post?.number} quotedByMap={quotedByMap} />
|
<Reply postReplyCount={replyCount} reply={reply} roles={roles} threadNumber={post?.number} quotedByMap={quotedByMap} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 = <T>(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;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/** Max replies loaded per page from useReplies. Threads with > this use Virtuoso. */
|
||||||
|
export const REPLIES_PER_PAGE = 500;
|
||||||
@@ -456,6 +456,8 @@
|
|||||||
|
|
||||||
.replyDesktop {
|
.replyDesktop {
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
|
content-visibility: auto;
|
||||||
|
contain-intrinsic-size: auto 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.replyDesktop .sideArrows {
|
.replyDesktop .sideArrows {
|
||||||
@@ -490,6 +492,8 @@
|
|||||||
|
|
||||||
.replyMobile {
|
.replyMobile {
|
||||||
padding-top: 7px;
|
padding-top: 7px;
|
||||||
|
content-visibility: auto;
|
||||||
|
contain-intrinsic-size: auto 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.replyMobile .replyContainer {
|
.replyMobile .replyContainer {
|
||||||
|
|||||||
Reference in New Issue
Block a user