diff --git a/src/hooks/use-account-subplebbits-with-metadata.ts b/src/hooks/use-account-subplebbits-with-metadata.ts index 3b89e2a7..8a1edeb9 100644 --- a/src/hooks/use-account-subplebbits-with-metadata.ts +++ b/src/hooks/use-account-subplebbits-with-metadata.ts @@ -1,15 +1,22 @@ -import { useAccountSubplebbits } from '@plebbit/plebbit-react-hooks'; -import { useMemo } from 'react'; +import useAccountsStore from '@plebbit/plebbit-react-hooks/dist/stores/accounts'; import { MultisubSubplebbit } from './use-default-subplebbits'; export const useAccountSubplebbitsWithMetadata = (): MultisubSubplebbit[] => { - const { accountSubplebbits } = useAccountSubplebbits(); + const subplebbitsWithMetadata = useAccountsStore( + (state) => { + const activeAccountId = state.activeAccountId; + const activeAccount = activeAccountId ? state.accounts[activeAccountId] : undefined; + const accountSubplebbits = activeAccount?.subplebbits || {}; + return Object.entries(accountSubplebbits).map(([address, sub]) => ({ + address, + title: (sub as any).title, + })); + }, + (prev, next) => { + if (prev.length !== next.length) return false; + return prev.every((item, idx) => item.address === next[idx].address && item.title === next[idx].title); + }, + ); - return useMemo(() => { - return Object.entries(accountSubplebbits || {}).map(([address, sub]) => ({ - address, - title: (sub as any).title, - // We can map other fields if needed - })); - }, [accountSubplebbits]); + return subplebbitsWithMetadata; }; diff --git a/src/views/mod-queue/mod-queue.tsx b/src/views/mod-queue/mod-queue.tsx index bdd66856..e81cca02 100644 --- a/src/views/mod-queue/mod-queue.tsx +++ b/src/views/mod-queue/mod-queue.tsx @@ -1,7 +1,8 @@ import React, { useMemo, useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams, Link } from 'react-router-dom'; -import { useAccountSubplebbits, useFeed, Comment, usePublishCommentModeration, useEditedComment } from '@plebbit/plebbit-react-hooks'; +import { useFeed, Comment, usePublishCommentModeration, useEditedComment } from '@plebbit/plebbit-react-hooks'; +import useAccountsStore from '@plebbit/plebbit-react-hooks/dist/stores/accounts'; import { Virtuoso } from 'react-virtuoso'; import { formatDistanceToNow } from 'date-fns'; import styles from './mod-queue.module.css'; @@ -26,11 +27,16 @@ interface ModQueueViewProps { interface ModQueueFooterProps { hasMore: boolean; - loadingStateString: string; + subplebbitAddresses: string[]; } // Defined outside ModQueueView to preserve component identity across renders (Virtuoso optimization) -const ModQueueFooter = ({ hasMore, loadingStateString }: ModQueueFooterProps) => { +// The useFeedStateString hook is called here instead of in ModQueueView to isolate re-renders +// caused by backend IPFS state changes to just this footer component +const ModQueueFooter = ({ hasMore, subplebbitAddresses }: ModQueueFooterProps) => { + const { t } = useTranslation(); + const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading'); + return hasMore ? (
@@ -51,7 +57,6 @@ const ModQueueRow = ({ comment, showBoardColumn = false }: ModQueueRowProps) => const { getAlertThresholdSeconds } = useModQueueStore(); const [initiatedAction, setInitiatedAction] = useState(null); - // handle pending mod or author edit const { editedComment } = useEditedComment({ comment }); const displayComment = editedComment || comment; @@ -253,8 +258,6 @@ interface ModQueueButtonProps { isMobile?: boolean; } -// Counter item that uses useEditedComment to get real-time moderation status -// and reports its status via callback interface ModQueueCountItemProps { comment: Comment; alertThresholdSeconds: number; @@ -270,7 +273,6 @@ const ModQueueCountItem = ({ comment, alertThresholdSeconds, onStatusChange }: M const timeWaiting = Date.now() / 1000 - timestamp; const isUrgent = isAwaiting && timeWaiting > alertThresholdSeconds; - // Report status changes to parent - useEffect is appropriate here for syncing with parent state useEffect(() => { onStatusChange(cid, { awaiting: isAwaiting, urgent: isUrgent }); }, [cid, isAwaiting, isUrgent, onStatusChange]); @@ -278,7 +280,6 @@ const ModQueueCountItem = ({ comment, alertThresholdSeconds, onStatusChange }: M return null; }; -// Inner component that handles counting with useEditedComment for each item interface ModQueueButtonContentProps { feed: Comment[]; alertThresholdSeconds: number; @@ -298,7 +299,6 @@ const ModQueueButtonContent = ({ feed, alertThresholdSeconds, boardIdentifier, i }); }, []); - // Calculate counts from status map const { normalCount, urgentCount } = useMemo(() => { let normal = 0; let urgent = 0; @@ -343,7 +343,6 @@ const ModQueueButtonContent = ({ feed, alertThresholdSeconds, boardIdentifier, i return ( <> - {/* Render counter items to track real-time moderation status */} {feed.map((item) => ( ))} @@ -354,11 +353,22 @@ const ModQueueButtonContent = ({ feed, alertThresholdSeconds, boardIdentifier, i export const ModQueueButton = ({ boardIdentifier, isMobile }: ModQueueButtonProps) => { const { getAlertThresholdSeconds } = useModQueueStore(); - const { accountSubplebbits } = useAccountSubplebbits(); - const accountSubplebbitAddresses = useMemo(() => Object.keys(accountSubplebbits || {}), [accountSubplebbits]); + + const accountSubplebbitAddresses = useAccountsStore( + (state) => { + const activeAccountId = state.activeAccountId; + const activeAccount = activeAccountId ? state.accounts[activeAccountId] : undefined; + const accountSubplebbits = activeAccount?.subplebbits || {}; + return Object.keys(accountSubplebbits); + }, + (prev, next) => { + if (prev.length !== next.length) return false; + return prev.every((val, idx) => val === next[idx]); + }, + ); + const defaultSubplebbits = useDefaultSubplebbits(); - // Resolve boardIdentifier to address if it exists const resolvedAddress = useMemo(() => { if (boardIdentifier) { return getSubplebbitAddress(boardIdentifier, defaultSubplebbits); @@ -397,13 +407,24 @@ export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueV const { t } = useTranslation(); const params = useParams(); const { selectedBoardFilter, alertThresholdValue, alertThresholdUnit, setAlertThreshold } = useModQueueStore(); - const { accountSubplebbits } = useAccountSubplebbits(); - const accountSubplebbitAddresses = Object.keys(accountSubplebbits); + + const accountSubplebbitAddresses = useAccountsStore( + (state) => { + const activeAccountId = state.activeAccountId; + const activeAccount = activeAccountId ? state.accounts[activeAccountId] : undefined; + const accountSubplebbits = activeAccount?.subplebbits || {}; + return Object.keys(accountSubplebbits); + }, + (prev, next) => { + if (prev.length !== next.length) return false; + return prev.every((val, idx) => val === next[idx]); + }, + ); + const defaultSubplebbits = useDefaultSubplebbits(); const boardIdentifier = propBoardIdentifier || params.boardIdentifier; - // Resolve boardIdentifier to address if it exists const resolvedAddress = useMemo(() => { if (boardIdentifier) { return getSubplebbitAddress(boardIdentifier, defaultSubplebbits); @@ -411,7 +432,6 @@ export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueV return undefined; }, [boardIdentifier, defaultSubplebbits]); - // Get metadata for filter dropdown const subplebbitsWithMetadata = useAccountSubplebbitsWithMetadata(); const subplebbitAddresses = useMemo(() => { @@ -438,15 +458,15 @@ export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueV setResetFunction(reset); }, [reset, setResetFunction]); - const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading'); const showBoardColumn = !resolvedAddress && !selectedBoardFilter; // Memoize footer components object to preserve identity across renders (Virtuoso optimization) + // Note: useFeedStateString is called inside ModQueueFooter to isolate re-renders from backend state changes const footerComponents = useMemo( () => ({ - Footer: () => , + Footer: () => , }), - [hasMore, loadingStateString], + [hasMore, subplebbitAddresses], ); return ( @@ -468,7 +488,6 @@ export const ModQueueView = ({ boardIdentifier: propBoardIdentifier }: ModQueueV value={alertThresholdUnit} onChange={(e) => { const newUnit = e.target.value as 'hours' | 'minutes'; - // Convert value when switching units const newValue = alertThresholdUnit === 'hours' && newUnit === 'minutes' ? alertThresholdValue * 60