From 9bbd72d179dc5a202d54cba1c910c33ea1a9c115 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Thu, 7 Oct 2021 02:40:40 +0800 Subject: [PATCH] Fix reaction-selector reference invalidation ## Issue When comments are refreshed, each `Comment` gets rendered 4-5 times due to reference invalidation for `othersReacts` (the data didn't actually change). ## Change For selectors without transformation, there is no need to memoize using `createSelector` -- just access it directly. Also, don't do things like `return a[id] || {}` in a reducer, because the reference to the empty object will be different on each call. Always return directly from the state so that the same reference is returned. This simple change avoided the wasted resources needed for `createSelector`, and reduced to render to just 2 (initial render, and when reactions are fetched). --- ui/component/comment/index.js | 4 +-- ui/component/commentReactions/index.js | 6 ++-- ui/component/commentReactions/view.jsx | 4 +-- ui/component/commentsList/index.js | 8 +++--- ui/redux/actions/comments.js | 8 +++--- ui/redux/selectors/comments.js | 39 +++++++++----------------- 6 files changed, 28 insertions(+), 41 deletions(-) diff --git a/ui/component/comment/index.js b/ui/component/comment/index.js index 712b0e34b..ffe06dd37 100644 --- a/ui/component/comment/index.js +++ b/ui/component/comment/index.js @@ -12,7 +12,7 @@ import { doSetPlayingUri } from 'redux/actions/content'; import { selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectLinkedCommentAncestors, - makeSelectOthersReactionsForComment, + selectOthersReactsForComment, makeSelectTotalReplyPagesForParentId, } from 'redux/selectors/comments'; import { selectActiveChannelClaim } from 'redux/selectors/app'; @@ -29,7 +29,7 @@ const select = (state, props) => { thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state), channelIsBlocked: props.authorUri && makeSelectChannelIsMuted(props.authorUri)(state), commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true, - othersReacts: makeSelectOthersReactionsForComment(reactionKey)(state), + othersReacts: selectOthersReactsForComment(state, reactionKey), activeChannelClaim, myChannels: selectMyChannelClaims(state), playingUri: selectPlayingUri(state), diff --git a/ui/component/commentReactions/index.js b/ui/component/commentReactions/index.js index 9f640d619..b2de27008 100644 --- a/ui/component/commentReactions/index.js +++ b/ui/component/commentReactions/index.js @@ -3,7 +3,7 @@ import Comment from './view'; import { makeSelectClaimIsMine, makeSelectClaimForUri } from 'redux/selectors/claims'; import { doResolveUri } from 'redux/actions/claims'; import { doToast } from 'redux/actions/notifications'; -import { makeSelectMyReactionsForComment, makeSelectOthersReactionsForComment } from 'redux/selectors/comments'; +import { selectMyReactsForComment, selectOthersReactsForComment } from 'redux/selectors/comments'; import { doCommentReact } from 'redux/actions/comments'; import { selectActiveChannelClaim } from 'redux/selectors/app'; @@ -15,8 +15,8 @@ const select = (state, props) => { return { claim: makeSelectClaimForUri(props.uri)(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state), - myReacts: makeSelectMyReactionsForComment(reactionKey)(state), - othersReacts: makeSelectOthersReactionsForComment(reactionKey)(state), + myReacts: selectMyReactsForComment(state, reactionKey), + othersReacts: selectOthersReactsForComment(state, reactionKey), activeChannelId, }; }; diff --git a/ui/component/commentReactions/view.jsx b/ui/component/commentReactions/view.jsx index a121e35ed..68187d907 100644 --- a/ui/component/commentReactions/view.jsx +++ b/ui/component/commentReactions/view.jsx @@ -73,12 +73,12 @@ export default function CommentReactions(props: Props) { const shouldHide = !canCreatorReact && hideCreatorLike; const creatorLiked = getCountForReact(REACTION_TYPES.CREATOR_LIKE) > 0; const likeIcon = SIMPLE_SITE - ? myReacts.includes(REACTION_TYPES.LIKE) + ? myReacts && myReacts.includes(REACTION_TYPES.LIKE) ? ICONS.FIRE_ACTIVE : ICONS.FIRE : ICONS.UPVOTE; const dislikeIcon = SIMPLE_SITE - ? myReacts.includes(REACTION_TYPES.DISLIKE) + ? myReacts && myReacts.includes(REACTION_TYPES.DISLIKE) ? ICONS.SLIME_ACTIVE : ICONS.SLIME : ICONS.DOWNVOTE; diff --git a/ui/component/commentsList/index.js b/ui/component/commentsList/index.js index d2e64843e..35fcc57c8 100644 --- a/ui/component/commentsList/index.js +++ b/ui/component/commentsList/index.js @@ -13,8 +13,8 @@ import { selectIsFetchingCommentsById, selectIsFetchingReacts, makeSelectTotalCommentsCountForUri, - selectOthersReactsById, - selectMyReactionsByCommentId, + selectOthersReacts, + selectMyReacts, makeSelectCommentIdsForUri, selectSettingsByChannelId, makeSelectPinnedCommentsForUri, @@ -47,8 +47,8 @@ const select = (state, props) => { isFetchingReacts: selectIsFetchingReacts(state), fetchingChannels: selectFetchingMyChannels(state), settingsByChannelId: selectSettingsByChannelId(state), - myReactsByCommentId: selectMyReactionsByCommentId(state), - othersReactsById: selectOthersReactsById(state), + myReactsByCommentId: selectMyReacts(state), + othersReactsById: selectOthersReacts(state), activeChannelId: activeChannelClaim && activeChannelClaim.claim_id, }; }; diff --git a/ui/redux/actions/comments.js b/ui/redux/actions/comments.js index 38ed48494..36c4dacc5 100644 --- a/ui/redux/actions/comments.js +++ b/ui/redux/actions/comments.js @@ -9,8 +9,8 @@ import { selectClaimsByUri, selectMyChannelClaims } from 'redux/selectors/claims import { doClaimSearch } from 'redux/actions/claims'; import { doToast, doSeeNotifications } from 'redux/actions/notifications'; import { - makeSelectMyReactionsForComment, - makeSelectOthersReactionsForComment, + selectMyReactsForComment, + selectOthersReactsForComment, selectPendingCommentReacts, selectModerationBlockList, selectModerationDelegatorsById, @@ -466,8 +466,8 @@ export function doCommentReact(commentId: string, type: string) { } const reactKey = `${commentId}:${activeChannelClaim.claim_id}`; - const myReacts = makeSelectMyReactionsForComment(reactKey)(state); - const othersReacts = makeSelectOthersReactionsForComment(reactKey)(state); + const myReacts = selectMyReactsForComment(state, reactKey) || []; + const othersReacts = selectOthersReactsForComment(state, reactKey) || {}; const signatureData = await channelSignName(activeChannelClaim.claim_id, activeChannelClaim.name); if (!signatureData) { diff --git a/ui/redux/selectors/comments.js b/ui/redux/selectors/comments.js index cd1a479bd..6f14085ba 100644 --- a/ui/redux/selectors/comments.js +++ b/ui/redux/selectors/comments.js @@ -6,6 +6,8 @@ import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc import { selectClaimsById, selectMyActiveClaims } from 'redux/selectors/claims'; import { isClaimNsfw } from 'util/claim'; +type State = { comments: CommentsState }; + const selectState = (state) => state.comments || {}; export const selectCommentsById = createSelector(selectState, (state) => state.commentById || {}); @@ -13,7 +15,17 @@ export const selectIsFetchingComments = createSelector(selectState, (state) => s export const selectIsFetchingCommentsById = createSelector(selectState, (state) => state.isLoadingById); export const selectIsFetchingCommentsByParentId = createSelector(selectState, (state) => state.isLoadingByParentId); export const selectIsFetchingReacts = createSelector(selectState, (state) => state.isFetchingReacts); -export const selectOthersReactsById = createSelector(selectState, (state) => state.othersReactsByCommentId); + +export const selectMyReacts = (state: State) => state.comments.myReactsByCommentId; +export const selectMyReactsForComment = (state: State, commentIdChannelId: string) => { + // @commentIdChannelId: Format = 'commentId:MyChannelId' + return state.comments.myReactsByCommentId && state.comments.myReactsByCommentId[commentIdChannelId]; +}; + +export const selectOthersReacts = (state: State) => state.comments.othersReactsByCommentId; +export const selectOthersReactsForComment = (state: State, id: string) => { + return state.comments.othersReactsByCommentId && state.comments.othersReactsByCommentId[id]; +}; export const selectPinnedCommentsById = createSelector(selectState, (state) => state.pinnedCommentsById); export const makeSelectPinnedCommentsForUri = (uri: string) => @@ -177,31 +189,6 @@ export const makeSelectCommentIdsForUri = (uri: string) => return state.byId[claimId]; }); -export const selectMyReactionsByCommentId = createSelector(selectState, (state) => state.myReactsByCommentId); - -/** - * makeSelectMyReactionsForComment - * - * @param commentIdChannelId Format = "commentId:MyChannelId". - */ -export const makeSelectMyReactionsForComment = (commentIdChannelId: string) => - createSelector(selectState, (state) => { - if (!state.myReactsByCommentId) { - return []; - } - - return state.myReactsByCommentId[commentIdChannelId] || []; - }); - -export const makeSelectOthersReactionsForComment = (commentId: string) => - createSelector(selectState, (state) => { - if (!state.othersReactsByCommentId) { - return {}; - } - - return state.othersReactsByCommentId[commentId] || {}; - }); - export const selectPendingCommentReacts = createSelector(selectState, (state) => state.pendingCommentReactions); export const selectSettingsByChannelId = createSelector(selectState, (state) => state.settingsByChannelId);