From 21b72c7607e429da5e54213a7ee3309465b6da26 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Thu, 13 Apr 2023 16:24:20 -0400 Subject: [PATCH] MM-50577 Replaced makeCreateAriaLabelForPost with better memoized usePostAriaLabel (#22833) --- .../post_view/post_aria_label_div.tsx | 18 +----- .../latest_post_reader.test.tsx | 14 +++++ .../latest_post_reader.tsx | 16 +---- .../src/selectors/entities/posts.ts | 4 +- webapp/channels/src/utils/post_utils.ts | 60 ++++++++++++------- 5 files changed, 62 insertions(+), 50 deletions(-) diff --git a/webapp/channels/src/components/post_view/post_aria_label_div.tsx b/webapp/channels/src/components/post_view/post_aria_label_div.tsx index c181ea4858..cdd02461dd 100644 --- a/webapp/channels/src/components/post_view/post_aria_label_div.tsx +++ b/webapp/channels/src/components/post_view/post_aria_label_div.tsx @@ -1,36 +1,24 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useRef} from 'react'; -import {useSelector} from 'react-redux'; -import {useIntl} from 'react-intl'; +import React from 'react'; import {Post} from '@mattermost/types/posts'; -import {GlobalState} from 'types/store'; - -import {makeCreateAriaLabelForPost} from 'utils/post_utils'; +import {usePostAriaLabel} from 'utils/post_utils'; export type Props = React.HTMLProps & { - labelPrefix?: string; post: Post; } const PostAriaLabelDiv = React.forwardRef((props: Props, ref: React.Ref) => { const { children, - labelPrefix, post, ...otherProps } = props; - const intl = useIntl(); - - const createAriaLabelForPost = useRef(makeCreateAriaLabelForPost()); - let ariaLabel = useSelector((state) => createAriaLabelForPost.current(state, post)(intl)); - if (labelPrefix) { - ariaLabel = labelPrefix + ariaLabel; - } + const ariaLabel = usePostAriaLabel(post); return (
{ expect(span.prop('children')).toContain(author.username); expect(span.prop('children')).toContain('enero'); }); + + test('should be able to handle an empty post array', () => { + const {mountOptions} = mockStore(baseState); + + const props = { + ...baseProps, + postIds: [], + }; + + const wrapper = mount(, mountOptions); + const span = wrapper.childAt(0); + + expect(span.prop('children')).toEqual(''); + }); }); diff --git a/webapp/channels/src/components/post_view/post_list_virtualized/latest_post_reader.tsx b/webapp/channels/src/components/post_view/post_list_virtualized/latest_post_reader.tsx index 240f39f844..dd314b949b 100644 --- a/webapp/channels/src/components/post_view/post_list_virtualized/latest_post_reader.tsx +++ b/webapp/channels/src/components/post_view/post_list_virtualized/latest_post_reader.tsx @@ -1,8 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useMemo, useRef} from 'react'; -import {useIntl} from 'react-intl'; +import React, {useMemo} from 'react'; import {useSelector} from 'react-redux'; import {getPost} from 'mattermost-redux/selectors/entities/posts'; @@ -11,27 +10,18 @@ import {Post} from '@mattermost/types/posts'; import {GlobalState} from 'types/store'; -import {getLatestPostId, makeCreateAriaLabelForPost} from 'utils/post_utils'; +import {getLatestPostId, usePostAriaLabel} from 'utils/post_utils'; interface Props { postIds?: string[]; } const LatestPostReader = (props: Props): JSX.Element => { - const intl = useIntl(); - const {postIds} = props; const latestPostId = useMemo(() => getLatestPostId(postIds || []), [postIds]); const latestPost = useSelector((state) => getPost(state, latestPostId)); - const createAriaLabelForPost = useRef(makeCreateAriaLabelForPost()); - const ariaLabel = useSelector((state) => { - if (!latestPost) { - return ''; - } - - return createAriaLabelForPost.current(state, latestPost)(intl); - }); + const ariaLabel = usePostAriaLabel(latestPost); return ( { [x: string]: Reaction; -} | undefined | null { +} | undefined { return createSelector('makeGetReactionsForPost', getReactionsForPosts, (state: GlobalState, postId: string) => postId, (reactions, postId) => { if (reactions[postId]) { return reactions[postId]; } - return null; + return undefined; }); } diff --git a/webapp/channels/src/utils/post_utils.ts b/webapp/channels/src/utils/post_utils.ts index f1312f2eb9..07e613d45e 100644 --- a/webapp/channels/src/utils/post_utils.ts +++ b/webapp/channels/src/utils/post_utils.ts @@ -1,7 +1,11 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {IntlShape} from 'react-intl'; +import {IntlShape, useIntl} from 'react-intl'; + +import {useMemo} from 'react'; + +import {useSelector} from 'react-redux'; import {createSelector} from 'reselect'; @@ -17,6 +21,8 @@ import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles' import {getCurrentTeamId, getTeam} from 'mattermost-redux/selectors/entities/teams'; import {makeGetDisplayName, getCurrentUserId, getUser, UserMentionKey, getUsersByUsername} from 'mattermost-redux/selectors/entities/users'; +import {memoizeResult} from 'mattermost-redux/utils/helpers'; + import {Channel} from '@mattermost/types/channels'; import {ClientConfig, ClientLicense} from '@mattermost/types/config'; import {ServerError} from '@mattermost/types/errors'; @@ -42,8 +48,8 @@ import {allAtMentions} from 'utils/text_formatting'; import {isMobile} from 'utils/user_agent'; import * as Utils from 'utils/utils'; -import * as Emoticons from './emoticons'; import EmojiMap from './emoji_map'; +import * as Emoticons from './emoticons'; const CHANNEL_SWITCH_IGNORE_ENTER_THRESHOLD_MS = 500; @@ -443,27 +449,41 @@ export function makeGetMentionsFromMessage(): (state: GlobalState, post: Post) = ); } -export function makeCreateAriaLabelForPost(): (state: GlobalState, post: Post) => (intl: IntlShape) => string { - const getReactionsForPost = makeGetUniqueReactionsToPost(); - const getDisplayName = makeGetDisplayName(); - const getMentionsFromMessage = makeGetMentionsFromMessage(); +export function usePostAriaLabel(post: Post | undefined) { + const intl = useIntl(); - return createSelector( - 'makeCreateAriaLabelForPost', - (state: GlobalState, post: Post) => post, - (state: GlobalState, post: Post) => getDisplayName(state, post.user_id), - (state: GlobalState, post: Post) => getReactionsForPost(state, post.id), - (state: GlobalState, post: Post) => get(state, Preferences.CATEGORY_FLAGGED_POST, post.id, null) != null, - getEmojiMap, - (state: GlobalState, post: Post) => getMentionsFromMessage(state, post), - (state: GlobalState) => getTeammateNameDisplaySetting(state), - (post, author, reactions, isFlagged, emojiMap, mentions, teammateNameDisplaySetting) => { - return (intl: IntlShape) => createAriaLabelForPost(post, author, isFlagged, reactions ?? {}, intl, emojiMap, mentions, teammateNameDisplaySetting); - }, - ); + const getDisplayName = useMemo(makeGetDisplayName, []); + const getReactionsForPost = useMemo(makeGetReactionsForPost, []); + const getMentionsFromMessage = useMemo(makeGetMentionsFromMessage, []); + + const createAriaLabelMemoized = memoizeResult(createAriaLabelForPost); + + return useSelector((state: GlobalState) => { + if (!post) { + return ''; + } + + const authorDisplayName = getDisplayName(state, post.user_id); + const reactions = getReactionsForPost(state, post?.id); + const isFlagged = get(state, Preferences.CATEGORY_FLAGGED_POST, post.id, null) != null; + const emojiMap = getEmojiMap(state); + const mentions = getMentionsFromMessage(state, post); + const teammateNameDisplaySetting = getTeammateNameDisplaySetting(state); + + return createAriaLabelMemoized( + post, + authorDisplayName, + isFlagged, + reactions, + intl, + emojiMap, + mentions, + teammateNameDisplaySetting, + ); + }); } -export function createAriaLabelForPost(post: Post, author: string, isFlagged: boolean, reactions: Record, intl: IntlShape, emojiMap: EmojiMap, mentions: Record, teammateNameDisplaySetting: string): string { +export function createAriaLabelForPost(post: Post, author: string, isFlagged: boolean, reactions: Record | undefined, intl: IntlShape, emojiMap: EmojiMap, mentions: Record, teammateNameDisplaySetting: string): string { const {formatMessage, formatTime, formatDate} = intl; let message = post.state === Posts.POST_DELETED ? formatMessage({