mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
perf(components): prevent rerenders from updatingState
Use Zustand selectors with custom equality functions to only subscribe to specific fields needed by components, avoiding unnecessary rerenders when transient state like updatingState changes. Extract offline indicators into separate components to isolate rerenders. Memoize card components in catalog and popular threads views.
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useLocation, useParams, useNavigate } from 'react-router-dom';
|
import { useLocation, useParams, useNavigate } from 'react-router-dom';
|
||||||
import { useAccount, useAccountComment } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
||||||
import Plebbit from '@plebbit/plebbit-js';
|
import useAccountsStore from '@plebbit/plebbit-react-hooks/dist/stores/accounts';
|
||||||
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
|
import Plebbit from '@plebbit/plebbit-js';
|
||||||
|
import { useStableSubplebbit } from '../../hooks/use-stable-subplebbit';
|
||||||
import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
|
import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
|
||||||
import styles from './board-header.module.css';
|
import styles from './board-header.module.css';
|
||||||
import { useMultisubMetadata, useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
|
import { useMultisubMetadata, useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
|
||||||
@@ -21,6 +23,26 @@ const ImageBanner = () => {
|
|||||||
return <img src={banner} alt='' />;
|
return <img src={banner} alt='' />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Separate component for offline indicator to isolate rerenders from updatingState
|
||||||
|
// Only this component will rerender when updatingState changes, not the whole BoardHeader
|
||||||
|
const OfflineIndicator = ({ subplebbitAddress }: { subplebbitAddress: string | undefined }) => {
|
||||||
|
// Subscribe to full subplebbit including transient state for offline detection
|
||||||
|
const subplebbit = useSubplebbitsStore((state) => (subplebbitAddress ? state.subplebbits[subplebbitAddress] : undefined));
|
||||||
|
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbit);
|
||||||
|
|
||||||
|
if (!isOffline && !isOnlineStatusLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={styles.offlineIconWrapper}>
|
||||||
|
<Tooltip content={offlineTitle}>
|
||||||
|
<span className={`${styles.offlineIcon} ${offlineIconClass}`} />
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const BoardHeader = () => {
|
const BoardHeader = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -33,9 +55,9 @@ const BoardHeader = () => {
|
|||||||
const resolvedAddress = useResolvedSubplebbitAddress();
|
const resolvedAddress = useResolvedSubplebbitAddress();
|
||||||
const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress;
|
const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress;
|
||||||
|
|
||||||
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
|
// Use stable subplebbit for display fields to avoid rerenders from updatingState
|
||||||
|
const stableSubplebbit = useStableSubplebbit(subplebbitAddress);
|
||||||
const { address, shortAddress } = subplebbit || {};
|
const { address, shortAddress } = stableSubplebbit || {};
|
||||||
|
|
||||||
const multisubMetadata = useMultisubMetadata();
|
const multisubMetadata = useMultisubMetadata();
|
||||||
const defaultSubplebbits = useDefaultSubplebbits();
|
const defaultSubplebbits = useDefaultSubplebbits();
|
||||||
@@ -43,9 +65,13 @@ const BoardHeader = () => {
|
|||||||
// Find matching subplebbit from default list to get its title
|
// Find matching subplebbit from default list to get its title
|
||||||
const defaultSubplebbit = subplebbitAddress ? defaultSubplebbits.find((s) => s.address === subplebbitAddress) : null;
|
const defaultSubplebbit = subplebbitAddress ? defaultSubplebbits.find((s) => s.address === subplebbitAddress) : null;
|
||||||
|
|
||||||
const account = useAccount() || {};
|
// Use accounts store with selector to only subscribe to subscriptions count
|
||||||
const subscriptions = account?.subscriptions || [];
|
const subscriptionsCount = useAccountsStore((state) => {
|
||||||
const subscriptionsSubtitle = t('subscriptions_subtitle', { count: subscriptions?.length || 0 });
|
const activeAccountId = state.activeAccountId;
|
||||||
|
const activeAccount = activeAccountId ? state.accounts[activeAccountId] : undefined;
|
||||||
|
return activeAccount?.subscriptions?.length || 0;
|
||||||
|
});
|
||||||
|
const subscriptionsSubtitle = t('subscriptions_subtitle', { count: subscriptionsCount });
|
||||||
|
|
||||||
const title = isInAllView
|
const title = isInAllView
|
||||||
? multisubMetadata?.title || '/all/ - 5chan Directories'
|
? multisubMetadata?.title || '/all/ - 5chan Directories'
|
||||||
@@ -53,11 +79,9 @@ const BoardHeader = () => {
|
|||||||
? '/subs/ - Subscriptions'
|
? '/subs/ - Subscriptions'
|
||||||
: isInModView
|
: isInModView
|
||||||
? _.startCase(t('boards_you_moderate'))
|
? _.startCase(t('boards_you_moderate'))
|
||||||
: defaultSubplebbit?.title || subplebbit?.title;
|
: defaultSubplebbit?.title || stableSubplebbit?.title;
|
||||||
const subtitle = isInAllView ? '' : isInSubscriptionsView ? subscriptionsSubtitle : isInModView ? '/mod/' : `${address || subplebbitAddress || ''}`;
|
const subtitle = isInAllView ? '' : isInSubscriptionsView ? subscriptionsSubtitle : isInModView ? '/mod/' : `${address || subplebbitAddress || ''}`;
|
||||||
|
|
||||||
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbit);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.content} ${shouldShowSnow() ? styles.garland : ''}`}>
|
<div className={`${styles.content} ${shouldShowSnow() ? styles.garland : ''}`}>
|
||||||
{!useIsMobile() && (
|
{!useIsMobile() && (
|
||||||
@@ -72,13 +96,7 @@ const BoardHeader = () => {
|
|||||||
? shortAddress.slice(0, -4)
|
? shortAddress.slice(0, -4)
|
||||||
: shortAddress
|
: shortAddress
|
||||||
: subplebbitAddress && Plebbit.getShortAddress({ address: subplebbitAddress }))}
|
: subplebbitAddress && Plebbit.getShortAddress({ address: subplebbitAddress }))}
|
||||||
{(isOffline || isOnlineStatusLoading) && !isInAllView && !isInSubscriptionsView && !isInModView && (
|
{!isInAllView && !isInSubscriptionsView && !isInModView && <OfflineIndicator subplebbitAddress={subplebbitAddress} />}
|
||||||
<span className={styles.offlineIconWrapper}>
|
|
||||||
<Tooltip content={offlineTitle}>
|
|
||||||
<span className={`${styles.offlineIcon} ${offlineIconClass}`} />
|
|
||||||
</Tooltip>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.boardSubtitle}>
|
<div className={styles.boardSubtitle}>
|
||||||
{isInSubscriptionsView ? (
|
{isInSubscriptionsView ? (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||||
@@ -109,7 +109,9 @@ export const CatalogPostMedia = ({ cid, commentMediaInfo, linkWidth, linkHeight
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CatalogPost = ({ post }: { post: Comment }) => {
|
// Memoize CatalogPost to prevent rerenders when parent rerenders due to updatingState
|
||||||
|
const CatalogPost = memo(
|
||||||
|
({ post }: { post: Comment }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, replyCount, spoiler, subplebbitAddress, timestamp, title, thumbnailUrl } = post || {};
|
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, replyCount, spoiler, subplebbitAddress, timestamp, title, thumbnailUrl } = post || {};
|
||||||
const linkCount = useCountLinksInReplies(post);
|
const linkCount = useCountLinksInReplies(post);
|
||||||
@@ -180,7 +182,7 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
|||||||
update();
|
update();
|
||||||
}, [update, windowWidth]);
|
}, [update, windowWidth]);
|
||||||
|
|
||||||
const { replies } = useReplies({ comment: post });
|
const { replies } = useReplies({ comment: post, flat: true });
|
||||||
const lastReply = replies?.length > 0 ? replies[replies.length - 1] : null;
|
const lastReply = replies?.length > 0 ? replies[replies.length - 1] : null;
|
||||||
|
|
||||||
const { isCommentAuthorMod: isCatalogPostAuthorMod, commentAuthorRole: catalogPostAuthorRole } = useEditCommentPrivileges({
|
const { isCommentAuthorMod: isCatalogPostAuthorMod, commentAuthorRole: catalogPostAuthorRole } = useEditCommentPrivileges({
|
||||||
@@ -304,7 +306,9 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
(prevProps, nextProps) => prevProps.post?.cid === nextProps.post?.cid,
|
||||||
|
);
|
||||||
|
|
||||||
interface CatalogRowProps {
|
interface CatalogRowProps {
|
||||||
index?: number;
|
index?: number;
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ const PostDesktop = ({ post, roles, showAllReplies, showReplies = true }: PostPr
|
|||||||
const { hidden, unhide, hide } = useHide({ cid });
|
const { hidden, unhide, hide } = useHide({ cid });
|
||||||
const isHidden = hidden && !isInPostPageView;
|
const isHidden = hidden && !isInPostPageView;
|
||||||
|
|
||||||
const { replies, hasMore, loadMore } = useReplies({ comment: post });
|
const { replies, hasMore, loadMore } = useReplies({ comment: post, flat: true });
|
||||||
const visiblelinksCount = useCountLinksInReplies(post, 5);
|
const visiblelinksCount = useCountLinksInReplies(post, 5);
|
||||||
const totalLinksCount = useCountLinksInReplies(post);
|
const totalLinksCount = useCountLinksInReplies(post);
|
||||||
const replyCount = replies?.length;
|
const replyCount = replies?.length;
|
||||||
|
|||||||
@@ -14,6 +14,19 @@ import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbi
|
|||||||
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
|
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
|
||||||
import useIsSubplebbitOffline from '../../hooks/use-is-subplebbit-offline';
|
import useIsSubplebbitOffline from '../../hooks/use-is-subplebbit-offline';
|
||||||
import usePublishPost from '../../hooks/use-publish-post';
|
import usePublishPost from '../../hooks/use-publish-post';
|
||||||
|
|
||||||
|
// Separate component for offline alert to isolate rerenders from updatingState
|
||||||
|
// Only this component will rerender when updatingState changes, not the whole PostForm
|
||||||
|
const OfflineAlert = ({ subplebbitAddress }: { subplebbitAddress: string | undefined }) => {
|
||||||
|
const subplebbit = useSubplebbitsStore((state) => (subplebbitAddress ? state.subplebbits[subplebbitAddress] : undefined));
|
||||||
|
const { isOffline, isOnlineStatusLoading, offlineTitle } = useIsSubplebbitOffline(subplebbit);
|
||||||
|
|
||||||
|
if (!isOffline && !isOnlineStatusLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={styles.offlineBoard}>{offlineTitle}</div>;
|
||||||
|
};
|
||||||
import usePublishReply from '../../hooks/use-publish-reply';
|
import usePublishReply from '../../hooks/use-publish-reply';
|
||||||
import FileUploader from '../../plugins/file-uploader';
|
import FileUploader from '../../plugins/file-uploader';
|
||||||
import styles from './post-form.module.css';
|
import styles from './post-form.module.css';
|
||||||
@@ -380,15 +393,11 @@ const PostForm = () => {
|
|||||||
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
|
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
|
||||||
const resolvedAddress = useResolvedSubplebbitAddress();
|
const resolvedAddress = useResolvedSubplebbitAddress();
|
||||||
const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress;
|
const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress;
|
||||||
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
|
|
||||||
const { isOffline, isOnlineStatusLoading, offlineTitle } = useIsSubplebbitOffline(subplebbit);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.postFormDesktop}>
|
<div className={styles.postFormDesktop}>
|
||||||
{!(isInAllView || isInSubscriptionsView || isInModView) && showForm && (isOffline || isOnlineStatusLoading) && (
|
{!(isInAllView || isInSubscriptionsView || isInModView) && showForm && <OfflineAlert subplebbitAddress={subplebbitAddress} />}
|
||||||
<div className={styles.offlineBoard}>{offlineTitle}</div>
|
|
||||||
)}
|
|
||||||
{isThreadClosed ? (
|
{isThreadClosed ? (
|
||||||
<div className={styles.closed}>
|
<div className={styles.closed}>
|
||||||
{t('thread_closed')}
|
{t('thread_closed')}
|
||||||
@@ -408,9 +417,7 @@ const PostForm = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.postFormMobile}>
|
<div className={styles.postFormMobile}>
|
||||||
{!(isInAllView || isInSubscriptionsView || isInModView) && showForm && (isOffline || isOnlineStatusLoading) && (
|
{!(isInAllView || isInSubscriptionsView || isInModView) && showForm && <OfflineAlert subplebbitAddress={subplebbitAddress} />}
|
||||||
<div className={styles.offlineBoard}>{offlineTitle}</div>
|
|
||||||
)}
|
|
||||||
{isThreadClosed ? (
|
{isThreadClosed ? (
|
||||||
<div className={styles.closed}>
|
<div className={styles.closed}>
|
||||||
{t('thread_closed')}
|
{t('thread_closed')}
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ const PostMediaContent = ({ post, link }: { post: any; link: string }) => {
|
|||||||
|
|
||||||
const ReplyBacklinks = ({ post }: PostProps) => {
|
const ReplyBacklinks = ({ post }: PostProps) => {
|
||||||
const { cid, parentCid } = post || {};
|
const { cid, parentCid } = post || {};
|
||||||
const { replies } = useReplies({ comment: post });
|
const { replies } = useReplies({ comment: post, flat: true });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
cid &&
|
cid &&
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
|
|||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { setAccount, useAccount } from '@plebbit/plebbit-react-hooks';
|
import { setAccount, useAccount } from '@plebbit/plebbit-react-hooks';
|
||||||
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
import { useSubplebbitField } from '../../hooks/use-stable-subplebbit';
|
||||||
import { formatMarkdown } from '../../lib/utils/post-utils';
|
import { formatMarkdown } from '../../lib/utils/post-utils';
|
||||||
import { getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
import { getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||||
import { isValidURL } from '../../lib/utils/url-utils';
|
import { isValidURL } from '../../lib/utils/url-utils';
|
||||||
@@ -140,9 +140,9 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, parentNumber, threa
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isInAllView = isAllView(location.pathname);
|
const isInAllView = isAllView(location.pathname);
|
||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
|
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
|
||||||
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
|
// Only subscribe to updatedAt to avoid rerenders from updatingState changes
|
||||||
const { updatedAt } = subplebbit || {};
|
const updatedAt = useSubplebbitField(subplebbitAddress, (subplebbit) => subplebbit?.updatedAt);
|
||||||
const isBoardOffline = subplebbit?.updatedAt && subplebbit.updatedAt < Date.now() / 1000 - 60 * 60;
|
const isBoardOffline = updatedAt && updatedAt < Date.now() / 1000 - 60 * 60;
|
||||||
const offlineAlert = updatedAt
|
const offlineAlert = updatedAt
|
||||||
? isBoardOffline && (
|
? isBoardOffline && (
|
||||||
<div className={styles.offlineBoard}>
|
<div className={styles.offlineBoard}>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { useAccountComment, useSubplebbitStats } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComment, useSubplebbitStats } from '@plebbit/plebbit-react-hooks';
|
||||||
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
import { useSubplebbitField } from '../../hooks/use-stable-subplebbit';
|
||||||
import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages';
|
import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages';
|
||||||
import useSubplebbitStatsVisibilityStore from '../../stores/use-subplebbit-stats-visibility-store';
|
import useSubplebbitStatsVisibilityStore from '../../stores/use-subplebbit-stats-visibility-store';
|
||||||
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
||||||
@@ -15,8 +15,9 @@ const SubplebbitStats = () => {
|
|||||||
const resolvedAddress = useResolvedSubplebbitAddress();
|
const resolvedAddress = useResolvedSubplebbitAddress();
|
||||||
const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress;
|
const subplebbitAddress = resolvedAddress || accountComment?.subplebbitAddress;
|
||||||
|
|
||||||
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
|
// Only subscribe to address and createdAt to avoid rerenders from updatingState changes
|
||||||
const { address, createdAt } = subplebbit || {};
|
const address = useSubplebbitField(subplebbitAddress, (subplebbit) => subplebbit?.address);
|
||||||
|
const createdAt = useSubplebbitField(subplebbitAddress, (subplebbit) => subplebbit?.createdAt);
|
||||||
|
|
||||||
let stats = useSubplebbitStats({ subplebbitAddress: address });
|
let stats = useSubplebbitStats({ subplebbitAddress: address });
|
||||||
const { hiddenStats, toggleVisibility } = useSubplebbitStatsVisibilityStore();
|
const { hiddenStats, toggleVisibility } = useSubplebbitStatsVisibilityStore();
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Plebbit from '@plebbit/plebbit-js';
|
import Plebbit from '@plebbit/plebbit-js';
|
||||||
import { useAccount, useAccountComment, useAccountSubplebbits } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
||||||
|
import useAccountsStore from '@plebbit/plebbit-react-hooks/dist/stores/accounts';
|
||||||
import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||||
import { useDefaultSubplebbits, MultisubSubplebbit } from '../../hooks/use-default-subplebbits';
|
import { useDefaultSubplebbits, MultisubSubplebbit } from '../../hooks/use-default-subplebbits';
|
||||||
import { useBoardPath, useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
import { useBoardPath, useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
||||||
@@ -87,7 +88,6 @@ const findBoardAddressByCode = (code: string, defaultSubplebbits: MultisubSubple
|
|||||||
|
|
||||||
const TopBarDesktop = () => {
|
const TopBarDesktop = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const account = useAccount();
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const isInCatalogView = isCatalogView(location.pathname, params);
|
const isInCatalogView = isCatalogView(location.pathname, params);
|
||||||
@@ -102,9 +102,34 @@ const TopBarDesktop = () => {
|
|||||||
// Memoize allBoardCodes since it's derived from a constant
|
// Memoize allBoardCodes since it's derived from a constant
|
||||||
const allBoardCodes = useMemo(() => getAllBoardCodes(), []);
|
const allBoardCodes = useMemo(() => getAllBoardCodes(), []);
|
||||||
|
|
||||||
const subscriptions = account?.subscriptions || [];
|
// Use accounts store with selective subscriptions to avoid rerenders from updatingState
|
||||||
const { accountSubplebbits } = useAccountSubplebbits();
|
// Only subscribe to subscriptions array and account subplebbit addresses
|
||||||
const accountSubplebbitAddresses = Object.keys(accountSubplebbits);
|
const subscriptions = useAccountsStore(
|
||||||
|
(state) => {
|
||||||
|
const activeAccountId = state.activeAccountId;
|
||||||
|
const activeAccount = activeAccountId ? state.accounts[activeAccountId] : undefined;
|
||||||
|
return activeAccount?.subscriptions || [];
|
||||||
|
},
|
||||||
|
(prev, next) => {
|
||||||
|
// Shallow compare arrays - only rerender if subscriptions actually change
|
||||||
|
if (prev.length !== next.length) return false;
|
||||||
|
return prev.every((val, idx) => val === next[idx]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
// Shallow compare arrays - only rerender if addresses actually change
|
||||||
|
if (prev.length !== next.length) return false;
|
||||||
|
return prev.every((val, idx) => val === next[idx]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Filter subscriptions to only show visible ones
|
// Filter subscriptions to only show visible ones
|
||||||
const visibleSubscriptionAddresses = subscriptions.filter((address: string) => visibleSubscriptions.has(address));
|
const visibleSubscriptionAddresses = subscriptions.filter((address: string) => visibleSubscriptions.has(address));
|
||||||
@@ -243,8 +268,21 @@ const TopBarMobile = ({ subplebbitAddress }: { subplebbitAddress: string }) => {
|
|||||||
const boardPath = useBoardPath(subplebbitAddress);
|
const boardPath = useBoardPath(subplebbitAddress);
|
||||||
const selectValue = isInAllView ? 'all' : isInSubscriptionsView ? 'subs' : boardPath || subplebbitAddress;
|
const selectValue = isInAllView ? 'all' : isInSubscriptionsView ? 'subs' : boardPath || subplebbitAddress;
|
||||||
|
|
||||||
const { accountSubplebbits } = useAccountSubplebbits();
|
// Use accounts store with selective subscriptions to avoid rerenders from updatingState
|
||||||
const accountSubplebbitAddresses = Object.keys(accountSubplebbits);
|
// Only subscribe to account subplebbit addresses (keys only)
|
||||||
|
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) => {
|
||||||
|
// Shallow compare arrays - only rerender if addresses actually change
|
||||||
|
if (prev.length !== next.length) return false;
|
||||||
|
return prev.every((val, idx) => val === next[idx]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Check if current subplebbit is a directory board
|
// Check if current subplebbit is a directory board
|
||||||
const currentIsDirectoryBoard = directoryBoards.some((board) => board.address === subplebbitAddress);
|
const currentIsDirectoryBoard = directoryBoards.some((board) => board.address === subplebbitAddress);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useAccount } from '@plebbit/plebbit-react-hooks';
|
import { useAccount } from '@plebbit/plebbit-react-hooks';
|
||||||
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
import { useSubplebbitField } from './use-stable-subplebbit';
|
||||||
|
|
||||||
interface AuthorPrivilegesProps {
|
interface AuthorPrivilegesProps {
|
||||||
commentAuthorAddress: string;
|
commentAuthorAddress: string;
|
||||||
@@ -11,8 +11,8 @@ interface AuthorPrivilegesProps {
|
|||||||
const useAuthorPrivileges = ({ commentAuthorAddress, subplebbitAddress }: AuthorPrivilegesProps) => {
|
const useAuthorPrivileges = ({ commentAuthorAddress, subplebbitAddress }: AuthorPrivilegesProps) => {
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const accountAuthorAddress = account?.author?.address;
|
const accountAuthorAddress = account?.author?.address;
|
||||||
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
|
// Only subscribe to roles field to avoid rerenders from updatingState changes
|
||||||
const { roles } = subplebbit || {};
|
const roles = useSubplebbitField(subplebbitAddress, (subplebbit) => subplebbit?.roles);
|
||||||
const { isCommentAuthorMod, isAccountMod, isAccountCommentAuthor, commentAuthorRole, accountAuthorRole } = useMemo(() => {
|
const { isCommentAuthorMod, isAccountMod, isAccountCommentAuthor, commentAuthorRole, accountAuthorRole } = useMemo(() => {
|
||||||
const commentAuthorRole = roles?.[commentAuthorAddress]?.role;
|
const commentAuthorRole = roles?.[commentAuthorAddress]?.role;
|
||||||
const isCommentAuthorMod = commentAuthorRole === 'admin' || commentAuthorRole === 'owner' || commentAuthorRole === 'moderator';
|
const isCommentAuthorMod = commentAuthorRole === 'admin' || commentAuthorRole === 'owner' || commentAuthorRole === 'moderator';
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { Subplebbit } from '@plebbit/plebbit-react-hooks';
|
import { Subplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils';
|
import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts popular posts from subplebbits.
|
||||||
|
* Uses memoization to avoid recomputing when only updatingState changes.
|
||||||
|
*/
|
||||||
const usePopularPosts = (subplebbits: Subplebbit[]) => {
|
const usePopularPosts = (subplebbits: Subplebbit[]) => {
|
||||||
const [popularPosts, setPopularPosts] = useState<Comment[]>([]);
|
// Track the previous CID list to detect actual content changes vs transient state changes
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const prevCidsRef = useRef<string>('');
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const { popularPosts, error } = useMemo(() => {
|
||||||
const fetchPopularPosts = () => {
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
|
||||||
setError(null);
|
|
||||||
const uniqueLinks: Set<string> = new Set();
|
const uniqueLinks: Set<string> = new Set();
|
||||||
const allPosts: Comment[] = [];
|
const allPosts: Comment[] = [];
|
||||||
|
|
||||||
@@ -22,33 +22,13 @@ const usePopularPosts = (subplebbits: Subplebbit[]) => {
|
|||||||
|
|
||||||
if (subplebbit?.posts?.pages?.hot?.comments) {
|
if (subplebbit?.posts?.pages?.hot?.comments) {
|
||||||
for (const post of Object.values(subplebbit.posts.pages.hot.comments as Comment)) {
|
for (const post of Object.values(subplebbit.posts.pages.hot.comments as Comment)) {
|
||||||
const {
|
const { deleted, link, linkHeight, linkWidth, locked, pinned, removed, replyCount, thumbnailUrl } = post;
|
||||||
deleted,
|
|
||||||
link,
|
|
||||||
linkHeight,
|
|
||||||
linkWidth,
|
|
||||||
locked,
|
|
||||||
pinned,
|
|
||||||
removed,
|
|
||||||
replyCount,
|
|
||||||
thumbnailUrl,
|
|
||||||
// timestamp
|
|
||||||
} = post;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||||
|
|
||||||
if (
|
if (hasThumbnail && replyCount > 1 && !deleted && !removed && !locked && !pinned && !uniqueLinks.has(link)) {
|
||||||
hasThumbnail &&
|
|
||||||
replyCount > 1 &&
|
|
||||||
!deleted &&
|
|
||||||
!removed &&
|
|
||||||
!locked &&
|
|
||||||
!pinned &&
|
|
||||||
// timestamp > Date.now() / 1000 - 60 * 60 * 24 * 30 &&
|
|
||||||
!uniqueLinks.has(link)
|
|
||||||
) {
|
|
||||||
subplebbitPosts.push(post);
|
subplebbitPosts.push(post);
|
||||||
uniqueLinks.add(link);
|
uniqueLinks.add(link);
|
||||||
}
|
}
|
||||||
@@ -64,19 +44,26 @@ const usePopularPosts = (subplebbits: Subplebbit[]) => {
|
|||||||
|
|
||||||
const sortedPosts = allPosts.sort((a: any, b: any) => b.timestamp - a.timestamp).slice(0, 8);
|
const sortedPosts = allPosts.sort((a: any, b: any) => b.timestamp - a.timestamp).slice(0, 8);
|
||||||
|
|
||||||
setPopularPosts(sortedPosts);
|
return { popularPosts: sortedPosts, error: null };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error in usePopularPosts:', err);
|
console.error('Error in usePopularPosts:', err);
|
||||||
setError('Failed to fetch popular posts');
|
return { popularPosts: [], error: 'Failed to fetch popular posts' };
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
fetchPopularPosts();
|
|
||||||
}, [subplebbits]);
|
}, [subplebbits]);
|
||||||
|
|
||||||
return { popularPosts, isLoading, error };
|
// Create stable reference: only update if the CIDs actually change
|
||||||
|
// This prevents unnecessary rerenders when only updatingState changes
|
||||||
|
const currentCids = popularPosts.map((p: any) => p.cid).join(',');
|
||||||
|
const stablePostsRef = useRef<Comment[]>(popularPosts);
|
||||||
|
|
||||||
|
if (currentCids !== prevCidsRef.current) {
|
||||||
|
prevCidsRef.current = currentCids;
|
||||||
|
stablePostsRef.current = popularPosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoading = stablePostsRef.current.length === 0;
|
||||||
|
|
||||||
|
return { popularPosts: stablePostsRef.current, isLoading, error };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default usePopularPosts;
|
export default usePopularPosts;
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom equality function that ignores transient state properties
|
||||||
|
* like updatingState, state, errors, etc. Only compares stable content fields.
|
||||||
|
*/
|
||||||
|
const isSubplebbitEqual = (prev: any, next: any): boolean => {
|
||||||
|
if (prev === next) return true;
|
||||||
|
if (!prev || !next) return prev === next;
|
||||||
|
|
||||||
|
// Compare only stable fields, ignore transient state
|
||||||
|
return (
|
||||||
|
prev.address === next.address &&
|
||||||
|
prev.title === next.title &&
|
||||||
|
prev.shortAddress === next.shortAddress &&
|
||||||
|
prev.roles === next.roles &&
|
||||||
|
prev.updatedAt === next.updatedAt &&
|
||||||
|
prev.createdAt === next.createdAt &&
|
||||||
|
prev.description === next.description
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get a subplebbit with stable reference that ignores updatingState changes.
|
||||||
|
* Use this when you only need content fields and don't care about loading states.
|
||||||
|
*
|
||||||
|
* @param subplebbitAddress - The address of the subplebbit to retrieve
|
||||||
|
* @returns The subplebbit object, or undefined if not found
|
||||||
|
*/
|
||||||
|
export const useStableSubplebbit = (subplebbitAddress: string | undefined) => {
|
||||||
|
// Use selector with custom equality to ignore transient state
|
||||||
|
const subplebbit = useSubplebbitsStore((state) => (subplebbitAddress ? state.subplebbits[subplebbitAddress] : undefined), isSubplebbitEqual);
|
||||||
|
|
||||||
|
return subplebbit;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get only specific fields from a subplebbit, ignoring updatingState.
|
||||||
|
* This is more efficient when you only need a few fields.
|
||||||
|
*
|
||||||
|
* @param subplebbitAddress - The address of the subplebbit
|
||||||
|
* @param selector - Function to extract the needed fields
|
||||||
|
* @returns The selected fields
|
||||||
|
*/
|
||||||
|
export const useSubplebbitField = <T>(subplebbitAddress: string | undefined, selector: (subplebbit: any) => T): T | undefined => {
|
||||||
|
const field = useSubplebbitsStore(
|
||||||
|
(state) => {
|
||||||
|
const subplebbit = subplebbitAddress ? state.subplebbits[subplebbitAddress] : undefined;
|
||||||
|
return subplebbit ? selector(subplebbit) : undefined;
|
||||||
|
},
|
||||||
|
(prev, next) => prev === next,
|
||||||
|
);
|
||||||
|
|
||||||
|
return field;
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
|
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
|
||||||
import { Comment, useAccount, useAccountComments, useAccountSubplebbits, useBlock, useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
import { Comment, useAccount, useAccountComments, useAccountSubplebbits, useBlock, useFeed } from '@plebbit/plebbit-react-hooks';
|
||||||
|
import { useStableSubplebbit, useSubplebbitField } from '../../hooks/use-stable-subplebbit';
|
||||||
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import styles from './board.module.css';
|
import styles from './board.module.css';
|
||||||
@@ -153,9 +154,13 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
|
|||||||
}
|
}
|
||||||
}, [filteredComments, reset]);
|
}, [filteredComments, reset]);
|
||||||
|
|
||||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
// Use stable subplebbit fields to avoid rerenders from updatingState
|
||||||
const { error, shortAddress, state } = subplebbit || {};
|
const subplebbitTitle = useSubplebbitField(subplebbitAddress, (sub) => sub?.title);
|
||||||
const title = isInAllView ? t('all') : isInSubscriptionsView ? t('subscriptions') : isInModView ? t('mod') : subplebbit?.title;
|
const shortAddress = useSubplebbitField(subplebbitAddress, (sub) => sub?.shortAddress);
|
||||||
|
// Only subscribe to state and error for footer display - these are needed
|
||||||
|
const stableSubplebbit = useStableSubplebbit(subplebbitAddress);
|
||||||
|
const { error, state } = stableSubplebbit || {};
|
||||||
|
const title = isInAllView ? t('all') : isInSubscriptionsView ? t('subscriptions') : isInModView ? t('mod') : subplebbitTitle;
|
||||||
|
|
||||||
const { blocked, unblock } = useBlock({ address: subplebbitAddress });
|
const { blocked, unblock } = useBlock({ address: subplebbitAddress });
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Comment, Subplebbit } from '@plebbit/plebbit-react-hooks';
|
import { Comment, Subplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
@@ -25,7 +25,9 @@ export const ContentPreview = ({ content, maxLength = 99 }: { content: string; m
|
|||||||
return truncatedText;
|
return truncatedText;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PopularThreadCard = ({ post, multisub }: PopularThreadProps) => {
|
// Memoize to prevent rerenders when parent rerenders due to updatingState
|
||||||
|
const PopularThreadCard = memo(
|
||||||
|
({ post, multisub }: PopularThreadProps) => {
|
||||||
const { cid, content, link, linkHeight, linkWidth, subplebbitAddress, thumbnailUrl, title } = post || {};
|
const { cid, content, link, linkHeight, linkWidth, subplebbitAddress, thumbnailUrl, title } = post || {};
|
||||||
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const defaultSubplebbits = useDefaultSubplebbits();
|
const defaultSubplebbits = useDefaultSubplebbits();
|
||||||
@@ -54,7 +56,10 @@ const PopularThreadCard = ({ post, multisub }: PopularThreadProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
// Custom equality: only rerender if post.cid changes
|
||||||
|
(prevProps, nextProps) => prevProps.post?.cid === nextProps.post?.cid,
|
||||||
|
);
|
||||||
|
|
||||||
const PopularThreadsBox = ({ multisub, subplebbits }: { multisub: MultisubSubplebbit[]; subplebbits: any }) => {
|
const PopularThreadsBox = ({ multisub, subplebbits }: { multisub: MultisubSubplebbit[]; subplebbits: any }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
import { useSubplebbitField } from '../../hooks/use-stable-subplebbit';
|
||||||
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
|
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
|
||||||
import { getSubplebbitAddress } from '../../lib/utils/route-utils';
|
import { getSubplebbitAddress } from '../../lib/utils/route-utils';
|
||||||
import { HomeLogo } from '../home';
|
import { HomeLogo } from '../home';
|
||||||
@@ -20,8 +20,9 @@ const NotFound = () => {
|
|||||||
const boardIdentifier = pathParts[0] && pathParts[0] !== 'not-found' && pathParts[0] !== 'faq' ? pathParts[0] : '';
|
const boardIdentifier = pathParts[0] && pathParts[0] !== 'not-found' && pathParts[0] !== 'faq' ? pathParts[0] : '';
|
||||||
const defaultSubplebbits = useDefaultSubplebbits();
|
const defaultSubplebbits = useDefaultSubplebbits();
|
||||||
const subplebbitAddress = boardIdentifier ? getSubplebbitAddress(boardIdentifier, defaultSubplebbits) : '';
|
const subplebbitAddress = boardIdentifier ? getSubplebbitAddress(boardIdentifier, defaultSubplebbits) : '';
|
||||||
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
|
// Only subscribe to address and shortAddress to avoid rerenders from updatingState changes
|
||||||
const { address, shortAddress } = subplebbit || {};
|
const address = useSubplebbitField(subplebbitAddress, (subplebbit) => subplebbit?.address);
|
||||||
|
const shortAddress = useSubplebbitField(subplebbitAddress, (subplebbit) => subplebbit?.shortAddress);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useEffect } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Comment, Role, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
import { Comment, Role, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
|
import { useSubplebbitField } from '../../hooks/use-stable-subplebbit';
|
||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { isAllView } from '../../lib/utils/view-utils';
|
import { isAllView } from '../../lib/utils/view-utils';
|
||||||
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
||||||
@@ -27,7 +28,8 @@ export interface PostProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Post = ({ post, showAllReplies = false, showReplies = true }: PostProps) => {
|
export const Post = ({ post, showAllReplies = false, showReplies = true }: PostProps) => {
|
||||||
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[post?.subplebbitAddress]);
|
// Only subscribe to roles field to avoid rerenders from updatingState changes
|
||||||
|
const roles = useSubplebbitField(post?.subplebbitAddress, (subplebbit) => subplebbit?.roles);
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
let comment = post;
|
let comment = post;
|
||||||
@@ -42,9 +44,9 @@ export const Post = ({ post, showAllReplies = false, showReplies = true }: PostP
|
|||||||
<div className={styles.thread}>
|
<div className={styles.thread}>
|
||||||
<div className={styles.postContainer}>
|
<div className={styles.postContainer}>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<PostMobile post={comment} roles={subplebbit?.roles} showAllReplies={showAllReplies} showReplies={showReplies} />
|
<PostMobile post={comment} roles={roles} showAllReplies={showAllReplies} showReplies={showReplies} />
|
||||||
) : (
|
) : (
|
||||||
<PostDesktop post={comment} roles={subplebbit?.roles} showAllReplies={showAllReplies} showReplies={showReplies} />
|
<PostDesktop post={comment} roles={roles} showAllReplies={showAllReplies} showReplies={showReplies} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user