From 5efeff3d507ab85758c26376b63fd7562f78c90d Mon Sep 17 00:00:00 2001 From: DomW Date: Wed, 5 Mar 2025 23:39:03 +0000 Subject: [PATCH] refactor(components): implement memoized selectors for denormalized channel (#2742) * refactor(components): implement memoized selectors * refactor: remove logs --- src/apps/feed/components/feed-chat/index.tsx | 5 +++-- src/apps/messenger/Main.tsx | 4 ++-- .../chat-view-container/chat-view-container.tsx | 5 +++-- .../member-management-dialog/container.tsx | 4 ++-- src/components/messenger/chat/index.tsx | 9 ++++----- .../messenger/conversation-actions/container.tsx | 6 ++++-- .../messenger/conversation-header/container.tsx | 5 +++-- src/components/messenger/feed/index.tsx | 5 +++-- .../member-management-menu/container.tsx | 5 +++-- src/components/messenger/message-info/container.tsx | 4 ++-- src/store/channels/selectors.ts | 10 ++++++++++ 11 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/apps/feed/components/feed-chat/index.tsx b/src/apps/feed/components/feed-chat/index.tsx index 0c3231712..fb766979f 100644 --- a/src/apps/feed/components/feed-chat/index.tsx +++ b/src/apps/feed/components/feed-chat/index.tsx @@ -3,7 +3,7 @@ import { RootState } from '../../../../store/reducer'; import { connectContainer } from '../../../../store/redux-container'; import { ChatViewContainer } from '../../../../components/chat-view-container/chat-view-container'; import { validateFeedChat } from '../../../../store/chat'; -import { Channel, denormalize, onRemoveReply } from '../../../../store/channels'; +import { Channel, onRemoveReply } from '../../../../store/channels'; import { MessageInput } from '../../../../components/message-input/container'; import { send as sendMessage } from '../../../../store/messages'; import { SendPayload as PayloadSendMessage } from '../../../../store/messages/saga'; @@ -18,6 +18,7 @@ import { toggleSecondarySidekick } from '../../../../store/group-management'; import { MembersSidekick } from '../../../../components/sidekick/variants/members-sidekick'; import { Spinner } from '@zero-tech/zui/components/LoadingIndicator'; import { ConversationActionsContainer } from '../../../../components/messenger/conversation-actions/container'; +import { denormalizedChannelSelector } from '../../../../store/channels/selectors'; import classNames from 'classnames'; import styles from './styles.module.scss'; @@ -54,7 +55,7 @@ export class Container extends React.Component { groupManagement, } = state; - const channel = denormalize(activeConversationId, state); + const channel = denormalizedChannelSelector(state, activeConversationId); const rawChannel = rawChannelSelector(activeConversationId)(state); return { diff --git a/src/apps/messenger/Main.tsx b/src/apps/messenger/Main.tsx index 04809cec5..3618f9b37 100644 --- a/src/apps/messenger/Main.tsx +++ b/src/apps/messenger/Main.tsx @@ -7,10 +7,10 @@ import { MessengerChat } from '../../components/messenger/chat'; import { MessengerFeed } from '../../components/messenger/feed'; import { DevPanelContainer } from '../../components/dev-panel/container'; import { FeatureFlag } from '../../components/feature-flag'; -import { denormalize } from '../../store/channels'; import { JoiningConversationDialog } from '../../components/joining-conversation-dialog'; import { ConversationsSidekick } from '../../components/sidekick/variants/conversations-sidekick'; import { MembersSidekick } from '../../components/sidekick/variants/members-sidekick'; +import { denormalizedChannelSelector } from '../../store/channels/selectors'; import styles from './Main.module.scss'; @@ -30,7 +30,7 @@ export class Container extends React.Component { chat: { activeConversationId, isJoiningConversation, isConversationsLoaded }, } = state; - const currentChannel = denormalize(activeConversationId, state) || null; + const currentChannel = denormalizedChannelSelector(state, activeConversationId) || null; return { isValidConversation: !!activeConversationId, diff --git a/src/components/chat-view-container/chat-view-container.tsx b/src/components/chat-view-container/chat-view-container.tsx index 529fb2370..7b4c81fb2 100644 --- a/src/components/chat-view-container/chat-view-container.tsx +++ b/src/components/chat-view-container/chat-view-container.tsx @@ -13,7 +13,7 @@ import { AdminMessageType, sendEmojiReaction, } from '../../store/messages'; -import { Channel, ConversationStatus, denormalize, onReply } from '../../store/channels'; +import { Channel, ConversationStatus, onReply } from '../../store/channels'; import { ChatView } from './chat-view'; import { AuthenticationState } from '../../store/authentication/types'; import { EditPayload, Payload as PayloadFetchMessages } from '../../store/messages/saga'; @@ -25,6 +25,7 @@ import { openMessageInfo } from '../../store/message-info'; import { toggleSecondarySidekick } from '../../store/group-management'; import { linkMessages, mapMessagesById, mapMessagesByRootId } from './utils'; import { openReportUserModal } from '../../store/report-user'; +import { denormalizedChannelSelector } from '../../store/channels/selectors'; export interface Properties extends PublicProperties { channel: Channel; @@ -63,7 +64,7 @@ export class Container extends React.Component { } static mapState(state: RootState, props: PublicProperties): Partial { - const channel = denormalize(props.channelId, state) || null; + const channel = denormalizedChannelSelector(state, props.channelId) || null; const { authentication: { user }, chat: { activeConversationId }, diff --git a/src/components/group-management/member-management-dialog/container.tsx b/src/components/group-management/member-management-dialog/container.tsx index d6d707d88..4041a7123 100644 --- a/src/components/group-management/member-management-dialog/container.tsx +++ b/src/components/group-management/member-management-dialog/container.tsx @@ -5,7 +5,6 @@ import { RootState } from '../../../store/reducer'; import { ConfirmationDefinition, MemberManagementDialog } from '.'; import { denormalize as denormalizeUser } from '../../../store/users'; import { displayName } from '../../../lib/user'; -import { denormalize as denormalizeChannel } from '../../../store/channels'; import { cancelMemberManagement, removeMember, @@ -14,6 +13,7 @@ import { setMemberAsModerator, removeMemberAsModerator, } from '../../../store/group-management'; +import { denormalizedChannelSelector } from '../../../store/channels/selectors'; export interface PublicProperties {} @@ -38,7 +38,7 @@ export class Container extends React.Component { groupManagement: { memberManagement }, } = state; const user = denormalizeUser(memberManagement.userId, state); - const channel = denormalizeChannel(memberManagement.roomId, state); + const channel = denormalizedChannelSelector(state, memberManagement.roomId); return { type: memberManagement.type, diff --git a/src/components/messenger/chat/index.tsx b/src/components/messenger/chat/index.tsx index 5343b40eb..f7f8a8938 100644 --- a/src/components/messenger/chat/index.tsx +++ b/src/components/messenger/chat/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import classNames from 'classnames'; import { RootState } from '../../../store/reducer'; import { connectContainer } from '../../../store/redux-container'; -import { Channel, denormalize, onAddLabel, onRemoveLabel, onRemoveReply } from '../../../store/channels'; +import { Channel, onAddLabel, onRemoveLabel, onRemoveReply } from '../../../store/channels'; import { ChatViewContainer } from '../../chat-view-container/chat-view-container'; import { send as sendMessage } from '../../../store/messages'; import { SendPayload as PayloadSendMessage } from '../../../store/messages/saga'; @@ -21,9 +21,9 @@ import { Media } from '../../message-input/utils'; import { ConversationHeaderContainer as ConversationHeader } from '../conversation-header/container'; import './styles.scss'; -import { rawChannelSelector } from '../../../store/channels/saga'; import { getOtherMembersTypingDisplayJSX } from '../lib/utils'; import { Panel, PanelBody } from '../../layout/panel'; +import { denormalizedChannelSelector } from '../../../store/channels/selectors'; export interface PublicProperties {} @@ -59,8 +59,7 @@ export class Container extends React.Component { groupManagement, } = state; - const directMessage = denormalize(activeConversationId, state); - const channel = rawChannelSelector(activeConversationId)(state); + const directMessage = denormalizedChannelSelector(state, activeConversationId); return { activeConversationId, @@ -68,7 +67,7 @@ export class Container extends React.Component { isJoiningConversation, isSecondarySidekickOpen: groupManagement.isSecondarySidekickOpen, leaveGroupDialogStatus: groupManagement.leaveGroupDialogStatus, - otherMembersTypingInRoom: channel?.otherMembersTyping || [], + otherMembersTypingInRoom: directMessage?.otherMembersTyping || [], }; } diff --git a/src/components/messenger/conversation-actions/container.tsx b/src/components/messenger/conversation-actions/container.tsx index 37f8b7066..41538f2c0 100644 --- a/src/components/messenger/conversation-actions/container.tsx +++ b/src/components/messenger/conversation-actions/container.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { RootState } from '../../../store/reducer'; import { connectContainer } from '../../../store/redux-container'; -import { Channel, DefaultRoomLabels, denormalize, onAddLabel, onRemoveLabel } from '../../../store/channels'; +import { Channel, DefaultRoomLabels, onAddLabel, onRemoveLabel } from '../../../store/channels'; import { currentUserSelector } from '../../../store/authentication/selectors'; import { startAddGroupMember, @@ -13,6 +13,8 @@ import { } from '../../../store/group-management'; import { ConversationActions } from '.'; import { openReportUserModal } from '../../../store/report-user'; +import { denormalizedChannelSelector } from '../../../store/channels/selectors'; + import './styles.scss'; export interface PublicProperties { @@ -48,7 +50,7 @@ export class Container extends React.Component { groupManagement, } = state; - const directMessage = denormalize(activeConversationId, state); + const directMessage = denormalizedChannelSelector(state, activeConversationId); const currentUser = currentUserSelector(state); const hasMultipleMembers = (directMessage?.otherMembers || []).length > 1; const isSocialChannel = directMessage?.isSocialChannel; diff --git a/src/components/messenger/conversation-header/container.tsx b/src/components/messenger/conversation-header/container.tsx index a27ffd653..9baf2117c 100644 --- a/src/components/messenger/conversation-header/container.tsx +++ b/src/components/messenger/conversation-header/container.tsx @@ -1,11 +1,12 @@ import React from 'react'; import { RootState } from '../../../store/reducer'; import { connectContainer } from '../../../store/redux-container'; -import { Channel, denormalize } from '../../../store/channels'; +import { Channel } from '../../../store/channels'; import { toggleSecondarySidekick } from '../../../store/group-management'; import { ConversationHeader } from '.'; import { ConversationActionsContainer as ConversationActions } from '../conversation-actions/container'; import { PanelHeader } from '../../layout/panel'; +import { denormalizedChannelSelector } from '../../../store/channels/selectors'; import { bemClassName } from '../../../lib/bem'; import './styles.scss'; @@ -32,7 +33,7 @@ export class Container extends React.Component { groupManagement, } = state; - const directMessage = denormalize(activeConversationId, state); + const directMessage = denormalizedChannelSelector(state, activeConversationId); return { activeConversationId, diff --git a/src/components/messenger/feed/index.tsx b/src/components/messenger/feed/index.tsx index 48b59a2c8..91297b97a 100644 --- a/src/components/messenger/feed/index.tsx +++ b/src/components/messenger/feed/index.tsx @@ -2,12 +2,13 @@ import React from 'react'; import { RootState } from '../../../store/reducer'; import { connectContainer } from '../../../store/redux-container'; import { PostPayload as PayloadPostMessage } from '../../../store/posts/saga'; -import { Channel, denormalize } from '../../../store/channels'; +import { Channel } from '../../../store/channels'; import { sendPost } from '../../../store/posts'; import { ConversationHeaderContainer as ConversationHeader } from '../conversation-header/container'; import { LeaveGroupDialogContainer } from '../../group-management/leave-group-dialog/container'; import { LeaveGroupDialogStatus, setLeaveGroupStatus } from '../../../store/group-management'; import { Switch, Route } from 'react-router-dom'; +import { denormalizedChannelSelector } from '../../../store/channels/selectors'; import { bemClassName } from '../../../lib/bem'; import './styles.scss'; @@ -41,7 +42,7 @@ export class Container extends React.Component { posts, } = state; - const currentChannel = denormalize(activeConversationId, state) || null; + const currentChannel = denormalizedChannelSelector(state, activeConversationId) || null; return { channel: currentChannel, diff --git a/src/components/messenger/group-management/member-management-menu/container.tsx b/src/components/messenger/group-management/member-management-menu/container.tsx index 1bf9803d1..79074fda5 100644 --- a/src/components/messenger/group-management/member-management-menu/container.tsx +++ b/src/components/messenger/group-management/member-management-menu/container.tsx @@ -3,11 +3,12 @@ import * as React from 'react'; import { RootState } from '../../../../store/reducer'; import { connectContainer } from '../../../../store/redux-container'; import { MemberManagementAction, openMemberManagement } from '../../../../store/group-management'; -import { User, denormalize as denormalizeChannel } from '../../../../store/channels'; +import { User } from '../../../../store/channels'; import { MemberManagementMenu } from '.'; import { isUserModerator } from '../../list/utils/utils'; import { currentUserSelector } from '../../../../store/authentication/selectors'; +import { denormalizedChannelSelector } from '../../../../store/channels/selectors'; export interface PublicProperties { user?: User; @@ -30,7 +31,7 @@ export class Container extends React.Component { chat: { activeConversationId }, } = state; - const conversation = denormalizeChannel(activeConversationId, state); + const conversation = denormalizedChannelSelector(state, activeConversationId); const conversationModeratorIds = conversation?.moderatorIds; const currentUser = currentUserSelector(state); diff --git a/src/components/messenger/message-info/container.tsx b/src/components/messenger/message-info/container.tsx index 5badd3b06..091621835 100644 --- a/src/components/messenger/message-info/container.tsx +++ b/src/components/messenger/message-info/container.tsx @@ -2,9 +2,9 @@ import React from 'react'; import { connectContainer } from '../../../store/redux-container'; import { RootState } from '../../../store/reducer'; import { closeMessageInfo } from '../../../store/message-info'; -import { denormalize as denormalizeChannel } from '../../../store/channels'; import { User } from '../../../store/channels'; import { OverviewPanel } from './overview-panel'; +import { denormalizedChannelSelector } from '../../../store/channels/selectors'; export interface PublicProperties {} @@ -26,7 +26,7 @@ export class Container extends React.Component { messageInfo: { selectedMessageId }, } = state; - const channel = denormalizeChannel(activeConversationId, state) || {}; + const channel = denormalizedChannelSelector(state, activeConversationId) || {}; const messages = channel.messages || []; const selectedMessage = messages.find((msg) => msg.id === selectedMessageId) || {}; const sentBy = selectedMessage?.sender?.userId !== user.data?.id ? selectedMessage?.sender : null; diff --git a/src/store/channels/selectors.ts b/src/store/channels/selectors.ts index a42c3412f..367dd76f9 100644 --- a/src/store/channels/selectors.ts +++ b/src/store/channels/selectors.ts @@ -1,6 +1,16 @@ import { RootState } from '../reducer'; import getDeepProperty from 'lodash.get'; +import { createSelector } from 'reselect'; +import { denormalize as denormalizeChannel } from '../channels'; export const rawChannel = (state: RootState, channelId: string) => { return getDeepProperty(state, `normalized.channels['${channelId}']`, null); }; + +// Memoized selector for denormalized channel +export const denormalizedChannelSelector = createSelector( + [(state: RootState) => state, (_state: RootState, channelId: string) => channelId], + (state, channelId) => { + return denormalizeChannel(channelId, state); + } +);