mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
perf(post-rendering): reduce quoted backlink rerenders via scoped store subscription
Replaced duplicated quotedByMap construction with useQuotedByMap() hook that subscribes only to post numbers referenced in each thread. Updated registerComments() to skip no-op writes, eliminating unnecessary numberToCid identity changes during feed scrolling.
This commit is contained in:
@@ -8,7 +8,7 @@ import styles from '../../views/post/post.module.css';
|
||||
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
||||
import { hashStringToColor, getTextColorForBackground } from '../../lib/utils/post-utils';
|
||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { isValidURL, QUOTE_NUMBER_REGEX } from '../../lib/utils/url-utils';
|
||||
import { isValidURL } from '../../lib/utils/url-utils';
|
||||
import { isAllView, isModQueueView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import { formatUserIDForDisplay } from '../../lib/utils/string-utils';
|
||||
import useModQueueStore from '../../stores/use-mod-queue-store';
|
||||
@@ -42,6 +42,7 @@ import useFeedResetStore from '../../stores/use-feed-reset-store';
|
||||
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';
|
||||
|
||||
const { addChallenge } = useChallengesStore.getState();
|
||||
|
||||
@@ -639,7 +640,6 @@ const PostDesktop = ({
|
||||
}
|
||||
}, [isInPostPageView, isInPendingPostView, reset, setResetFunction]);
|
||||
const registerComments = usePostNumberStore((s) => s.registerComments);
|
||||
const numberToCid = usePostNumberStore((s) => s.numberToCid);
|
||||
const prevCidsRef = useRef<string>('');
|
||||
useEffect(() => {
|
||||
const all = post ? [post, ...(replies || [])] : replies || [];
|
||||
@@ -669,36 +669,7 @@ const PostDesktop = ({
|
||||
// Filter out deleted replies with no children for both virtuoso and non-virtuoso rendering
|
||||
const filteredReplies = useMemo(() => (replies || []).filter((reply) => !(reply.deleted && (reply.replyCount === 0 || !reply.replyCount))), [replies]);
|
||||
|
||||
// Compute quotedByMap: map each quoted CID to array of replies that quote it
|
||||
const quotedByMap = useMemo(() => {
|
||||
const map = new Map<string, Comment[]>();
|
||||
for (const reply of filteredReplies) {
|
||||
const cidSet = new Set<string>();
|
||||
|
||||
if (reply.quotedCids?.length) {
|
||||
for (const quotedCid of reply.quotedCids) {
|
||||
cidSet.add(quotedCid);
|
||||
}
|
||||
}
|
||||
|
||||
if (reply.content) {
|
||||
for (const match of reply.content.matchAll(QUOTE_NUMBER_REGEX)) {
|
||||
const postNumber = parseInt(match[1], 10);
|
||||
const quotedCid = numberToCid[postNumber];
|
||||
if (quotedCid) {
|
||||
cidSet.add(quotedCid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const quotedCid of cidSet) {
|
||||
const arr = map.get(quotedCid);
|
||||
if (arr) arr.push(reply);
|
||||
else map.set(quotedCid, [reply]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}, [filteredReplies, numberToCid]);
|
||||
const quotedByMap = useQuotedByMap(filteredReplies);
|
||||
|
||||
// Virtuoso scroll position management for infinite replies
|
||||
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
|
||||
|
||||
@@ -9,7 +9,6 @@ import { shouldShowSnow } from '../../lib/snow';
|
||||
import { getHasThumbnail } from '../../lib/utils/media-utils';
|
||||
import { getTextColorForBackground, hashStringToColor } from '../../lib/utils/post-utils';
|
||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { QUOTE_NUMBER_REGEX } from '../../lib/utils/url-utils';
|
||||
import { isAllView, isModQueueView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import { formatUserIDForDisplay } from '../../lib/utils/string-utils';
|
||||
import useModQueueStore from '../../stores/use-mod-queue-store';
|
||||
@@ -37,6 +36,7 @@ import useChallengesStore from '../../stores/use-challenges-store';
|
||||
import useFeedResetStore from '../../stores/use-feed-reset-store';
|
||||
import usePostNumberStore from '../../stores/use-post-number-store';
|
||||
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
||||
import useQuotedByMap from '../../hooks/use-quoted-by-map';
|
||||
|
||||
const { addChallenge } = useChallengesStore.getState();
|
||||
|
||||
@@ -489,7 +489,6 @@ const PostMobile = ({
|
||||
}
|
||||
}, [isInPostView, isInPendingPostView, reset, setResetFunction]);
|
||||
const registerComments = usePostNumberStore((s) => s.registerComments);
|
||||
const numberToCid = usePostNumberStore((s) => s.numberToCid);
|
||||
const prevCidsRef = useRef<string>('');
|
||||
useEffect(() => {
|
||||
const all = post ? [post, ...(replies || [])] : replies || [];
|
||||
@@ -512,36 +511,7 @@ const PostMobile = ({
|
||||
// Filter out deleted replies with no children for both virtuoso and non-virtuoso rendering
|
||||
const filteredReplies = useMemo(() => (replies || []).filter((reply) => !(reply.deleted && (reply.replyCount === 0 || !reply.replyCount))), [replies]);
|
||||
|
||||
// Compute quotedByMap: map each quoted CID to array of replies that quote it
|
||||
const quotedByMap = useMemo(() => {
|
||||
const map = new Map<string, Comment[]>();
|
||||
for (const reply of filteredReplies) {
|
||||
const cidSet = new Set<string>();
|
||||
|
||||
if (reply.quotedCids?.length) {
|
||||
for (const quotedCid of reply.quotedCids) {
|
||||
cidSet.add(quotedCid);
|
||||
}
|
||||
}
|
||||
|
||||
if (reply.content) {
|
||||
for (const match of reply.content.matchAll(QUOTE_NUMBER_REGEX)) {
|
||||
const postNumber = parseInt(match[1], 10);
|
||||
const quotedCid = numberToCid[postNumber];
|
||||
if (quotedCid) {
|
||||
cidSet.add(quotedCid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const quotedCid of cidSet) {
|
||||
const arr = map.get(quotedCid);
|
||||
if (arr) arr.push(reply);
|
||||
else map.set(quotedCid, [reply]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}, [filteredReplies, numberToCid]);
|
||||
const quotedByMap = useQuotedByMap(filteredReplies);
|
||||
|
||||
// Virtuoso scroll position management for infinite replies
|
||||
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Comment } from '@plebbit/plebbit-react-hooks';
|
||||
import { QUOTE_NUMBER_REGEX } from '../lib/utils/url-utils';
|
||||
import usePostNumberStore from '../stores/use-post-number-store';
|
||||
|
||||
interface ReplyQuoteTargets {
|
||||
reply: Comment;
|
||||
quotedPostNumbers: number[];
|
||||
}
|
||||
|
||||
const extractReplyQuoteTargets = (replies: Comment[]) => {
|
||||
const quotedPostNumbers = new Set<number>();
|
||||
const replyQuoteTargets: ReplyQuoteTargets[] = [];
|
||||
|
||||
for (const reply of replies) {
|
||||
const quotedPostNumbersForReply: number[] = [];
|
||||
|
||||
if (reply.content) {
|
||||
for (const match of reply.content.matchAll(QUOTE_NUMBER_REGEX)) {
|
||||
const postNumber = Number.parseInt(match[1], 10);
|
||||
if (!Number.isNaN(postNumber)) {
|
||||
quotedPostNumbersForReply.push(postNumber);
|
||||
quotedPostNumbers.add(postNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
replyQuoteTargets.push({
|
||||
reply,
|
||||
quotedPostNumbers: quotedPostNumbersForReply,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
replyQuoteTargets,
|
||||
quotedPostNumbers: [...quotedPostNumbers].sort((a, b) => a - b),
|
||||
};
|
||||
};
|
||||
|
||||
const useQuotedByMap = (replies: Comment[] = []) => {
|
||||
const { replyQuoteTargets, quotedPostNumbers } = useMemo(() => extractReplyQuoteTargets(replies), [replies]);
|
||||
|
||||
// Subscribe only to post numbers referenced in this thread to avoid unrelated global store churn.
|
||||
const quotedNumbersSignature = usePostNumberStore(
|
||||
useCallback((state) => quotedPostNumbers.map((postNumber) => `${postNumber}:${state.numberToCid[postNumber] ?? ''}`).join('|'), [quotedPostNumbers]),
|
||||
);
|
||||
|
||||
const quotedNumberToCid = useMemo(() => {
|
||||
if (quotedPostNumbers.length === 0) {
|
||||
return {} as Record<number, string>;
|
||||
}
|
||||
|
||||
const { numberToCid } = usePostNumberStore.getState();
|
||||
const nextQuotedNumberToCid: Record<number, string> = {};
|
||||
|
||||
for (const postNumber of quotedPostNumbers) {
|
||||
const quotedCid = numberToCid[postNumber];
|
||||
if (quotedCid) {
|
||||
nextQuotedNumberToCid[postNumber] = quotedCid;
|
||||
}
|
||||
}
|
||||
|
||||
return nextQuotedNumberToCid;
|
||||
}, [quotedPostNumbers, quotedNumbersSignature]);
|
||||
|
||||
return useMemo(() => {
|
||||
const map = new Map<string, Comment[]>();
|
||||
|
||||
for (const { reply, quotedPostNumbers: quotedNumbersForReply } of replyQuoteTargets) {
|
||||
const cidSet = new Set<string>();
|
||||
|
||||
if (reply.quotedCids?.length) {
|
||||
for (const quotedCid of reply.quotedCids) {
|
||||
cidSet.add(quotedCid);
|
||||
}
|
||||
}
|
||||
|
||||
for (const postNumber of quotedNumbersForReply) {
|
||||
const quotedCid = quotedNumberToCid[postNumber];
|
||||
if (quotedCid) {
|
||||
cidSet.add(quotedCid);
|
||||
}
|
||||
}
|
||||
|
||||
for (const quotedCid of cidSet) {
|
||||
const repliesQuotingCid = map.get(quotedCid);
|
||||
if (repliesQuotingCid) {
|
||||
repliesQuotingCid.push(reply);
|
||||
} else {
|
||||
map.set(quotedCid, [reply]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}, [replyQuoteTargets, quotedNumberToCid]);
|
||||
};
|
||||
|
||||
export default useQuotedByMap;
|
||||
@@ -14,18 +14,28 @@ const usePostNumberStore = create<PostNumberState>((set) => ({
|
||||
if (!comments?.length) return;
|
||||
|
||||
set((state) => {
|
||||
const nextNumberToCid = { ...state.numberToCid };
|
||||
const nextCidToNumber = { ...state.cidToNumber };
|
||||
let nextNumberToCid = state.numberToCid;
|
||||
let nextCidToNumber = state.cidToNumber;
|
||||
let hasUpdates = false;
|
||||
|
||||
for (const c of comments) {
|
||||
const num = c?.number;
|
||||
const cid = c?.cid;
|
||||
if (typeof num === 'number' && cid) {
|
||||
if (typeof num === 'number' && cid && (nextNumberToCid[num] !== cid || nextCidToNumber[cid] !== num)) {
|
||||
if (!hasUpdates) {
|
||||
nextNumberToCid = { ...state.numberToCid };
|
||||
nextCidToNumber = { ...state.cidToNumber };
|
||||
hasUpdates = true;
|
||||
}
|
||||
nextNumberToCid[num] = cid;
|
||||
nextCidToNumber[cid] = num;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasUpdates) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return { numberToCid: nextNumberToCid, cidToNumber: nextCidToNumber };
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user