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).
This commit is contained in:
infinite-persistence 2021-10-07 02:40:40 +08:00
parent 249b73f8c6
commit 9bbd72d179
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
6 changed files with 28 additions and 41 deletions

View file

@ -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),

View file

@ -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,
};
};

View file

@ -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;

View file

@ -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,
};
};

View file

@ -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) {

View file

@ -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);