From 698df59f197eefc4f5da63fa4f98b6d7a7714ea9 Mon Sep 17 00:00:00 2001 From: plebeius Date: Fri, 5 Dec 2025 17:38:17 +0100 Subject: [PATCH] perf(components): memoize post menus with minimal props --- src/components/catalog-row/catalog-row.tsx | 6 ++-- src/components/post-desktop/post-desktop.tsx | 6 ++-- .../post-menu-desktop/post-menu-desktop.tsx | 32 ++++++++--------- .../post-menu-mobile/post-menu-mobile.tsx | 34 +++++++++++-------- src/components/post-mobile/post-mobile.tsx | 6 ++-- src/lib/utils/post-menu-props.ts | 33 ++++++++++++++++++ 6 files changed, 79 insertions(+), 38 deletions(-) create mode 100644 src/lib/utils/post-menu-props.ts diff --git a/src/components/catalog-row/catalog-row.tsx b/src/components/catalog-row/catalog-row.tsx index e3569adf..084a2616 100644 --- a/src/components/catalog-row/catalog-row.tsx +++ b/src/components/catalog-row/catalog-row.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { Link, useLocation, useParams } from 'react-router-dom'; @@ -24,6 +24,7 @@ import { ContentPreview } from '../../views/home/popular-threads-box'; import PostMenuDesktop from '../post-desktop/post-menu-desktop'; import styles from './catalog-row.module.css'; import _ from 'lodash'; +import { selectPostMenuProps } from '../../lib/utils/post-menu-props'; interface CatalogPostMediaProps { cid: string; @@ -142,6 +143,7 @@ const CatalogPost = ({ post }: { post: Comment }) => { const isInSubscriptionsView = isSubscriptionsView(location.pathname, params); const defaultSubplebbits = useDefaultSubplebbits(); const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : ''; + const postMenuProps = useMemo(() => selectPostMenuProps(post), [post]); const postLink = isInAllView && isDescription ? '/all/description' : `/${boardPath}/${isDescription ? 'description' : isRules ? 'rules' : `thread/${cid}`}`; @@ -287,7 +289,7 @@ const CatalogPost = ({ post }: { post: Comment }) => { )} - +
{(showOPComment || isTextOnlyThread) && (hasThumbnail ? postContent : {postContent})}
diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx index b1743adc..bc30671b 100644 --- a/src/components/post-desktop/post-desktop.tsx +++ b/src/components/post-desktop/post-desktop.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { Link, useLocation, useParams } from 'react-router-dom'; import { Comment, useAuthorAvatar, useEditedComment } from '@plebbit/plebbit-react-hooks'; @@ -33,6 +33,7 @@ import { create } from 'zustand'; import _ from 'lodash'; import { shouldShowSnow } from '../../lib/snow'; import useReplyModalStore from '../../stores/use-reply-modal-store'; +import { selectPostMenuProps } from '../../lib/utils/post-menu-props'; interface ShowOmittedRepliesState { showOmittedReplies: Record; @@ -66,6 +67,7 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden }: PostProps) => { const { hideAvatars } = useAvatarVisibilityStore(); const defaultSubplebbits = useDefaultSubplebbits(); const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : undefined; + const postMenuProps = useMemo(() => selectPostMenuProps(post), [post]); const params = useParams(); const location = useLocation(); @@ -226,7 +228,7 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden }: PostProps) => { )} - {!(removed || deleted) && } + {!(removed || deleted) && } {cid && parentCid && replies && diff --git a/src/components/post-desktop/post-menu-desktop/post-menu-desktop.tsx b/src/components/post-desktop/post-menu-desktop/post-menu-desktop.tsx index d4b0abcc..961a41b8 100644 --- a/src/components/post-desktop/post-menu-desktop/post-menu-desktop.tsx +++ b/src/components/post-desktop/post-menu-desktop/post-menu-desktop.tsx @@ -1,9 +1,9 @@ -import { useState } from 'react'; +import { memo, useState } from 'react'; import { createPortal } from 'react-dom'; import { useLocation, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react'; -import { Comment, useBlock } from '@plebbit/plebbit-react-hooks'; +import { useBlock } from '@plebbit/plebbit-react-hooks'; import styles from './post-menu-desktop.module.css'; import { getCommentMediaInfo } from '../../../lib/utils/media-utils'; import { copyShareLinkToClipboard, isValidURL, type ShareLinkType } from '../../../lib/utils/url-utils'; @@ -12,14 +12,7 @@ import { useDefaultSubplebbits } from '../../../hooks/use-default-subplebbits'; import { isAllView, isCatalogView, isPostPageView, isSubscriptionsView } from '../../../lib/utils/view-utils'; import useHide from '../../../hooks/use-hide'; import _ from 'lodash'; - -interface PostMenuDesktopProps { - cid: string; - isDescription?: boolean; - isRules?: boolean; - subplebbitAddress: string; - onClose: () => void; -} +import { PostMenuProps } from '../../../lib/utils/post-menu-props'; const CopyLinkButton = ({ cid, subplebbitAddress, linkType, onClose }: { cid?: string; subplebbitAddress: string; linkType: ShareLinkType; onClose: () => void }) => { const { t } = useTranslation(); @@ -92,14 +85,19 @@ const BlockBoardButton = ({ address }: { address: string }) => { ); }; -const PostMenuDesktop = ({ post }: { post: Comment }) => { +type PostMenuDesktopProps = { + postMenu: PostMenuProps; +}; + +const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => { + console.log('postMenu rerender', postMenu); const { t } = useTranslation(); - const { author, cid, isDescription, isRules, link, thumbnailUrl, linkWidth, linkHeight, postCid, subplebbitAddress } = post || {}; - const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight); + const { authorAddress, cid, isDescription, isRules, link, thumbnailUrl, linkWidth, linkHeight, postCid, subplebbitAddress } = postMenu || {}; + const commentMediaInfo = getCommentMediaInfo(link || '', thumbnailUrl || '', linkWidth ?? 0, linkHeight ?? 0); const { thumbnail, type, url } = commentMediaInfo || {}; const [menuBtnRotated, setMenuBtnRotated] = useState(false); - const { hidden, unhide, hide } = useHide({ cid }); + const { hidden, unhide, hide } = useHide({ cid: cid || '' }); const location = useLocation(); const params = useParams(); @@ -161,8 +159,8 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => { )} {link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && } - {!isDescription && !isRules && } - {(isInAllView || isInSubscriptionsView) && } + {!isDescription && !isRules && authorAddress && } + {(isInAllView || isInSubscriptionsView) && subplebbitAddress && } , document.body, @@ -171,4 +169,4 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => { ); }; -export default PostMenuDesktop; +export default memo(PostMenuDesktop); diff --git a/src/components/post-mobile/post-menu-mobile/post-menu-mobile.tsx b/src/components/post-mobile/post-menu-mobile/post-menu-mobile.tsx index 42b0ece3..520dcd58 100644 --- a/src/components/post-mobile/post-menu-mobile/post-menu-mobile.tsx +++ b/src/components/post-mobile/post-menu-mobile/post-menu-mobile.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { memo, useState } from 'react'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { Comment, useBlock } from '@plebbit/plebbit-react-hooks'; @@ -13,16 +13,14 @@ import useHide from '../../../hooks/use-hide'; import EditMenu from '../../edit-menu/edit-menu'; import { isBoardView, isPostPageView } from '../../../lib/utils/view-utils'; import { useLocation, useParams } from 'react-router-dom'; +import { PostMenuProps } from '../../../lib/utils/post-menu-props'; -interface PostMenuMobileProps { - cid: string; - isDescription?: boolean; +type HideButtonProps = { + cid?: string; isReply?: boolean; - isRules?: boolean; postCid?: string; - subplebbitAddress?: string; onClose?: () => void; -} +}; const CopyLinkButton = ({ cid, subplebbitAddress, linkType, onClose }: { cid?: string; subplebbitAddress: string; linkType: ShareLinkType; onClose: () => void }) => { const { t } = useTranslation(); @@ -57,7 +55,7 @@ const ImageSearchButtons = ({ url, onClose }: { url: string; onClose: () => void ); }; -const HidePostButton = ({ cid, isReply, onClose, postCid }: PostMenuMobileProps) => { +const HidePostButton = ({ cid, isReply, onClose, postCid }: HideButtonProps) => { const { t } = useTranslation(); const { hide, hidden, unhide } = useHide({ cid }); const isInPostView = isPostPageView(useLocation().pathname, useParams()); @@ -98,9 +96,15 @@ const BlockBoardButton = ({ address }: { address: string }) => { ); }; -const PostMenuMobile = ({ post }: { post: Comment }) => { - const { author, cid, deleted, isDescription, isRules, link, linkHeight, linkWidth, parentCid, postCid, removed, subplebbitAddress, thumbnailUrl } = post || {}; - const { isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({ commentAuthorAddress: author?.address, subplebbitAddress }); +type PostMenuMobileProps = { + postMenu: PostMenuProps; + editMenuPost: Comment; +}; + +const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => { + const { authorAddress, cid, deleted, isDescription, isRules, link, linkHeight, linkWidth, parentCid, postCid, removed, subplebbitAddress, thumbnailUrl } = + postMenu || {}; + const { isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({ commentAuthorAddress: authorAddress, subplebbitAddress }); const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight); const { thumbnail, type, url } = commentMediaInfo || {}; const [isMenuOpen, setIsMenuOpen] = useState(false); @@ -142,8 +146,8 @@ const PostMenuMobile = ({ post }: { post: Comment }) => { {cid && subplebbitAddress && } {!cid && isDescription && subplebbitAddress && } {!cid && isRules && subplebbitAddress && } - {cid && subplebbitAddress && } - {cid && subplebbitAddress && !isDescription && !isRules && } + {cid && subplebbitAddress && } + {cid && subplebbitAddress && !isDescription && !isRules && authorAddress && } {cid && subplebbitAddress && !isInBoardView && !isDescription && !isRules && } {link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && } @@ -154,11 +158,11 @@ const PostMenuMobile = ({ post }: { post: Comment }) => { )} {(isAccountMod || isAccountCommentAuthor) && cid && ( - + )} ); }; -export default PostMenuMobile; +export default memo(PostMenuMobile); diff --git a/src/components/post-mobile/post-mobile.tsx b/src/components/post-mobile/post-mobile.tsx index e158758a..87ab6f93 100644 --- a/src/components/post-mobile/post-mobile.tsx +++ b/src/components/post-mobile/post-mobile.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link, useLocation, useParams } from 'react-router-dom'; import { Comment, useAuthorAvatar, useEditedComment } from '@plebbit/plebbit-react-hooks'; @@ -28,6 +28,7 @@ import Tooltip from '../tooltip'; import { PostProps } from '../../views/post/post'; import _ from 'lodash'; import useReplyModalStore from '../../stores/use-reply-modal-store'; +import { selectPostMenuProps } from '../../lib/utils/post-menu-props'; const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => { const { t } = useTranslation(); @@ -71,6 +72,7 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => { const hasThumbnail = getHasThumbnail(commentMediaInfo, link); const stateString = useStateString(post); + const postMenuProps = useMemo(() => selectPostMenuProps(post), [post]); const handleUserAddressClick = useAuthorAddressClick(); const numberOfPostsByAuthor = document.querySelectorAll(`[data-author-address="${shortAddress}"][data-post-cid="${postCid}"]`).length; @@ -98,7 +100,7 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => { return ( <>
- + diff --git a/src/lib/utils/post-menu-props.ts b/src/lib/utils/post-menu-props.ts new file mode 100644 index 00000000..c1bc50d1 --- /dev/null +++ b/src/lib/utils/post-menu-props.ts @@ -0,0 +1,33 @@ +import { Comment } from '@plebbit/plebbit-react-hooks'; + +export type PostMenuProps = { + cid?: string; + postCid?: string; + parentCid?: string; + subplebbitAddress?: string; + isDescription?: boolean; + isRules?: boolean; + authorAddress?: string; + link?: string; + linkWidth?: number; + linkHeight?: number; + thumbnailUrl?: string; + deleted?: boolean; + removed?: boolean; +}; + +export const selectPostMenuProps = (post?: Comment): PostMenuProps => ({ + cid: post?.cid, + postCid: post?.postCid, + parentCid: post?.parentCid, + subplebbitAddress: post?.subplebbitAddress, + isDescription: post?.isDescription, + isRules: post?.isRules, + authorAddress: post?.author?.address, + link: post?.link, + linkWidth: post?.linkWidth, + linkHeight: post?.linkHeight, + thumbnailUrl: post?.thumbnailUrl, + deleted: post?.deleted, + removed: post?.removed, +});