From 26f89b3ec9bdd86419a66dd56c58e52a2520c26f Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Fri, 8 Oct 2021 09:57:48 +0800 Subject: [PATCH 1/5] re-reselect proof of concept + fix Date selector as first example `makeSelectDataForUri` always returns a new reference, so `ClaimPreview` was constantly being rendered. It's pretty expensive since `ClaimPreview`'s rendering checks against a huge blocklist, which is another issue on it's own. - This commit tests the usage of `re-reselect` as the solution to the multi-instance memoization problem (https://github.com/toomuchdesign/re-reselect/blob/master/examples/1-join-selectors.md) --- package.json | 1 + ui/component/claimPreview/index.js | 9 ++--- ui/component/claimPreviewTile/index.js | 4 +- ui/component/dateTime/index.js | 4 +- ui/redux/selectors/claims.js | 55 ++++++++++++++++++++++---- yarn.lock | 5 +++ 6 files changed, 62 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index d1b3ee739..505fd3041 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "match-sorter": "^6.3.0", "parse-duration": "^1.0.0", "proxy-polyfill": "0.1.6", + "re-reselect": "^4.0.0", "react-datetime-picker": "^3.2.1", "react-plastic": "^1.1.1", "react-top-loading-bar": "^2.0.1", diff --git a/ui/component/claimPreview/index.js b/ui/component/claimPreview/index.js index b63696f69..25ab46a8b 100644 --- a/ui/component/claimPreview/index.js +++ b/ui/component/claimPreview/index.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import { - makeSelectClaimForUri, + selectClaimForUri, makeSelectIsUriResolving, makeSelectClaimIsMine, makeSelectClaimIsPending, @@ -8,7 +8,7 @@ import { makeSelectReflectingClaimForUri, makeSelectClaimWasPurchased, makeSelectTitleForUri, - makeSelectDateForUri, + selectDateForUri, } from 'redux/selectors/claims'; import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info'; import { @@ -30,14 +30,14 @@ import ClaimPreview from './view'; import formatMediaDuration from 'util/formatMediaDuration'; const select = (state, props) => { - const claim = props.uri && makeSelectClaimForUri(props.uri)(state); + const claim = props.uri && selectClaimForUri(state, props.uri); const media = claim && claim.value && (claim.value.video || claim.value.audio); const mediaDuration = media && media.duration && formatMediaDuration(media.duration, { screenReader: true }); return { claim, mediaDuration, - date: props.uri && makeSelectDateForUri(props.uri)(state), + date: props.uri && selectDateForUri(state, props.uri), title: props.uri && makeSelectTitleForUri(props.uri)(state), pending: props.uri && makeSelectClaimIsPending(props.uri)(state), reflectingProgress: props.uri && makeSelectReflectingClaimForUri(props.uri)(state), @@ -45,7 +45,6 @@ const select = (state, props) => { claimIsMine: props.uri && makeSelectClaimIsMine(props.uri)(state), isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state), isResolvingRepost: props.uri && makeSelectIsUriResolving(props.repostUrl)(state), - repostClaim: props.uri && makeSelectClaimForUri(props.uri)(state), nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state), blackListedOutpoints: selectBlackListedOutpoints(state), filteredOutpoints: selectFilteredOutpoints(state), diff --git a/ui/component/claimPreviewTile/index.js b/ui/component/claimPreviewTile/index.js index b3be8f36d..eaf64a865 100644 --- a/ui/component/claimPreviewTile/index.js +++ b/ui/component/claimPreviewTile/index.js @@ -6,7 +6,7 @@ import { makeSelectTitleForUri, makeSelectChannelForClaimUri, makeSelectClaimIsNsfw, - makeSelectDateForUri, + selectDateForUri, } from 'redux/selectors/claims'; import { doFileGet } from 'redux/actions/file'; import { doResolveUri } from 'redux/actions/claims'; @@ -24,7 +24,7 @@ const select = (state, props) => { return { claim, mediaDuration, - date: props.uri && makeSelectDateForUri(props.uri)(state), + date: props.uri && selectDateForUri(state, props.uri), channel: props.uri && makeSelectChannelForClaimUri(props.uri)(state), isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state), thumbnail: props.uri && makeSelectThumbnailForUri(props.uri)(state), diff --git a/ui/component/dateTime/index.js b/ui/component/dateTime/index.js index 367ac4ab1..24fc98280 100644 --- a/ui/component/dateTime/index.js +++ b/ui/component/dateTime/index.js @@ -1,11 +1,11 @@ import { connect } from 'react-redux'; -import { makeSelectDateForUri } from 'redux/selectors/claims'; +import { selectDateForUri } from 'redux/selectors/claims'; import * as SETTINGS from 'constants/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings'; import DateTime from './view'; const select = (state, props) => ({ - date: props.date || makeSelectDateForUri(props.uri)(state), + date: props.date || selectDateForUri(state, props.uri), clock24h: makeSelectClientSetting(SETTINGS.CLOCK_24H)(state), }); export default connect(select)(DateTime); diff --git a/ui/redux/selectors/claims.js b/ui/redux/selectors/claims.js index 6cb544820..06f26fd1f 100644 --- a/ui/redux/selectors/claims.js +++ b/ui/redux/selectors/claims.js @@ -2,20 +2,22 @@ import { normalizeURI, parseURI, isURIValid } from 'util/lbryURI'; import { selectSupportsByOutpoint } from 'redux/selectors/wallet'; import { createSelector } from 'reselect'; +import { createCachedSelector } from 're-reselect'; import { isClaimNsfw, filterClaims } from 'util/claim'; import * as CLAIM from 'constants/claim'; +type State = { claims: any }; + const selectState = (state) => state.claims || {}; -export const selectById = createSelector(selectState, (state) => state.byId || {}); - -export const selectPendingClaimsById = createSelector(selectState, (state) => state.pendingById || {}); +export const selectById = (state: State) => selectState(state).byId || {}; +export const selectPendingClaimsById = (state: State) => selectState(state).pendingById || {}; export const selectClaimsById = createSelector(selectById, selectPendingClaimsById, (byId, pendingById) => { return Object.assign(byId, pendingById); // do I need merged to keep metadata? }); -export const selectClaimIdsByUri = createSelector(selectState, (state) => state.claimsByUri || {}); +export const selectClaimIdsByUri = (state: State) => selectState(state).claimsByUri || {}; export const selectCurrentChannelPage = createSelector(selectState, (state) => state.currentChannelPage || 1); @@ -74,6 +76,43 @@ export const selectReflectingById = createSelector(selectState, (state) => state export const makeSelectClaimForClaimId = (claimId: string) => createSelector(selectClaimsById, (byId) => byId[claimId]); +export const selectClaimForUri = createCachedSelector( + selectClaimIdsByUri, + selectClaimsById, + (state, uri) => uri, + (state, uri, returnRepost = true) => returnRepost, + (byUri, byId, uri, returnRepost) => { + const validUri = isURIValid(uri); + + if (validUri && byUri) { + const claimId = uri && byUri[normalizeURI(uri)]; + const claim = byId[claimId]; + + // Make sure to return the claim as is so apps can check if it's been resolved before (null) or still needs to be resolved (undefined) + if (claimId === null) { + return null; + } else if (claimId === undefined) { + return undefined; + } + + const repostedClaim = claim && claim.reposted_claim; + if (repostedClaim && returnRepost) { + const channelUrl = + claim.signing_channel && (claim.signing_channel.canonical_url || claim.signing_channel.permanent_url); + + return { + ...repostedClaim, + repost_url: normalizeURI(uri), + repost_channel_url: channelUrl, + repost_bid_amount: claim && claim.meta && claim.meta.effective_amount, + }; + } else { + return claim; + } + } + } +)((state, uri, returnRepost = true) => `${uri}:${returnRepost ? '1' : '0'}`); + export const makeSelectClaimForUri = (uri: string, returnRepost: boolean = true) => createSelector(selectClaimIdsByUri, selectClaimsById, (byUri, byId) => { const validUri = isURIValid(uri); @@ -242,8 +281,9 @@ export const makeSelectMetadataItemForUri = (uri: string, key: string) => export const makeSelectTitleForUri = (uri: string) => createSelector(makeSelectMetadataForUri(uri), (metadata) => metadata && metadata.title); -export const makeSelectDateForUri = (uri: string) => - createSelector(makeSelectClaimForUri(uri), (claim) => { +export const selectDateForUri = createCachedSelector( + selectClaimForUri, // input: (state, uri, ?returnRepost) + (claim) => { const timestamp = claim && claim.value && @@ -257,7 +297,8 @@ export const makeSelectDateForUri = (uri: string) => } const dateObj = new Date(timestamp); return dateObj; - }); + } +)((state, uri) => uri); export const makeSelectAmountForUri = (uri: string) => createSelector(makeSelectClaimForUri(uri), (claim) => { diff --git a/yarn.lock b/yarn.lock index 66c53b697..571bdedf8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13179,6 +13179,11 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +re-reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/re-reselect/-/re-reselect-4.0.0.tgz#9ddec4c72c4d952f68caa5aa4b76a9ed38b75cac" + integrity sha512-wuygyq8TXUlSdVXv2kigXxQNOgdb9m7LbIjwfTNGSpaY1riLd5e+VeQjlQMyUtrk0oiyhi1AqIVynworl3qxHA== + react-async-script@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/react-async-script/-/react-async-script-1.1.1.tgz#f481c6c5f094bf4b94a9d52da0d0dda2e1a74bdf" From 29b845c3fc6e98081df7b55e0db8ab950b90bfa8 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Fri, 8 Oct 2021 13:52:30 +0800 Subject: [PATCH 2/5] Optimize tags and followedTags followedTags: - Moved the filtering to the reducer side, so that we don't do it every time. We can't rely on `createSelector` because the store will be invalidated on each `USER_STATE_POPULATE`, unfortunately. tags: - Memoize via re-reselect for the "ForUri" selector. --- ui/component/claimTags/index.js | 4 ++-- ui/redux/reducers/tags.js | 13 ++++++++++++- ui/redux/selectors/claims.js | 9 +++++++++ ui/redux/selectors/tags.js | 16 ++++++++-------- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/ui/component/claimTags/index.js b/ui/component/claimTags/index.js index a0280f138..385aca981 100644 --- a/ui/component/claimTags/index.js +++ b/ui/component/claimTags/index.js @@ -1,10 +1,10 @@ import { connect } from 'react-redux'; -import { makeSelectTagsForUri } from 'redux/selectors/claims'; +import { selectTagsForUri } from 'redux/selectors/claims'; import { selectFollowedTags } from 'redux/selectors/tags'; import ClaimTags from './view'; const select = (state, props) => ({ - tags: makeSelectTagsForUri(props.uri)(state), + tags: selectTagsForUri(state, props.uri), followedTags: selectFollowedTags(state), }); diff --git a/ui/redux/reducers/tags.js b/ui/redux/reducers/tags.js index 4edd4fbf7..c3be1baf2 100644 --- a/ui/redux/reducers/tags.js +++ b/ui/redux/reducers/tags.js @@ -65,11 +65,22 @@ export default handleActions( [ACTIONS.USER_STATE_POPULATE]: (state: TagState, action: { data: { tags: ?Array } }) => { const { tags } = action.data; if (Array.isArray(tags)) { + const next = tags && tags.filter((tag) => typeof tag === 'string'); + const prev = state.followedTags; + + if (next && prev && prev.length === next.length && prev.every((value, index) => value === next[index])) { + // No changes + return state; + } + + // New state return { ...state, - followedTags: tags, + followedTags: next || [], }; } + + // Purge 'followedTags' return { ...state, }; diff --git a/ui/redux/selectors/claims.js b/ui/redux/selectors/claims.js index 06f26fd1f..45f12936e 100644 --- a/ui/redux/selectors/claims.js +++ b/ui/redux/selectors/claims.js @@ -267,6 +267,11 @@ export const makeSelectTotalPagesInChannelSearch = (uri: string) => return byChannel['pageCount']; }); +export const selectMetadataForUri = createCachedSelector(selectClaimForUri, (claim, uri) => { + const metadata = claim && claim.value; + return metadata || (claim === undefined ? undefined : null); +})((state, uri) => uri); + export const makeSelectMetadataForUri = (uri: string) => createSelector(makeSelectClaimForUri(uri), (claim) => { const metadata = claim && claim.value; @@ -534,6 +539,10 @@ export const makeSelectMyChannelPermUrlForName = (name: string) => return matchingClaim ? matchingClaim.permanent_url : null; }); +export const selectTagsForUri = createCachedSelector(selectMetadataForUri, (metadata: ?GenericMetadata) => { + return (metadata && metadata.tags) || []; +})((state, uri) => uri); + export const makeSelectTagsForUri = (uri: string) => createSelector(makeSelectMetadataForUri(uri), (metadata: ?GenericMetadata) => { return (metadata && metadata.tags) || []; diff --git a/ui/redux/selectors/tags.js b/ui/redux/selectors/tags.js index c75fbaa39..1651f14d9 100644 --- a/ui/redux/selectors/tags.js +++ b/ui/redux/selectors/tags.js @@ -1,16 +1,16 @@ // @flow import { createSelector } from 'reselect'; -const selectState = (state: { tags: TagState }) => state.tags || {}; +type State = { tags: TagState }; + +const selectState = (state: State) => state.tags || {}; export const selectKnownTagsByName = createSelector(selectState, (state: TagState): KnownTags => state.knownTags); -export const selectFollowedTagsList = createSelector(selectState, (state: TagState): Array => - state.followedTags.filter(tag => typeof tag === 'string') -); +export const selectFollowedTagsList = (state: State) => selectState(state).followedTags; export const selectFollowedTags = createSelector(selectFollowedTagsList, (followedTags: Array): Array => - followedTags.map(tag => ({ name: tag.toLowerCase() })).sort((a, b) => a.name.localeCompare(b.name)) + followedTags.map((tag) => ({ name: tag.toLowerCase() })).sort((a, b) => a.name.localeCompare(b.name)) ); export const selectUnfollowedTags = createSelector( @@ -19,7 +19,7 @@ export const selectUnfollowedTags = createSelector( (tagsByName: KnownTags, followedTags: Array): Array => { const followedTagsSet = new Set(followedTags); let tagsToReturn = []; - Object.keys(tagsByName).forEach(key => { + Object.keys(tagsByName).forEach((key) => { if (!followedTagsSet.has(key)) { const { name } = tagsByName[key]; tagsToReturn.push({ name: name.toLowerCase() }); @@ -31,6 +31,6 @@ export const selectUnfollowedTags = createSelector( ); export const makeSelectIsFollowingTag = (tag: string) => - createSelector(selectFollowedTags, followedTags => { - return followedTags.some(followedTag => followedTag.name === tag.toLowerCase()); + createSelector(selectFollowedTags, (followedTags) => { + return followedTags.some((followedTag) => followedTag.name === tag.toLowerCase()); }); From e7572312a8e3f931f166e78e065eb3f78f45fa56 Mon Sep 17 00:00:00 2001 From: zeppi Date: Thu, 2 Dec 2021 23:19:47 -0500 Subject: [PATCH 3/5] comment react selectors --- flow-typed/Comment.js | 1 + static/app-strings.json | 2 + ui/redux/selectors/comments.js | 116 +++++++++++++-------------------- 3 files changed, 47 insertions(+), 72 deletions(-) diff --git a/flow-typed/Comment.js b/flow-typed/Comment.js index c851d297d..0a497a324 100644 --- a/flow-typed/Comment.js +++ b/flow-typed/Comment.js @@ -45,6 +45,7 @@ declare type CommentsState = { isLoading: boolean, isLoadingById: boolean, isLoadingByParentId: { [string]: boolean }, + isCommenting: boolean, myComments: ?Set, isFetchingReacts: boolean, myReactsByCommentId: ?{ [string]: Array }, // {"CommentId:MyChannelId": ["like", "dislike", ...]} diff --git a/static/app-strings.json b/static/app-strings.json index b36842566..b3527b9b3 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2220,5 +2220,7 @@ "filtered": "filtered", "View All Playlists": "View All Playlists", "Your wallet is not currently using a cloud sync service. You are in control of backing up your wallet.": "Your wallet is not currently using a cloud sync service. You are in control of backing up your wallet.", + "Sending": "Sending", + "You sent %lbc%": "You sent %lbc%", "--end--": "--end--" } diff --git a/ui/redux/selectors/comments.js b/ui/redux/selectors/comments.js index 045bf001d..f71eecdb8 100644 --- a/ui/redux/selectors/comments.js +++ b/ui/redux/selectors/comments.js @@ -6,16 +6,28 @@ 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 || {}); -export const selectIsFetchingComments = createSelector(selectState, (state) => state.isLoading); -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 selectCommentsById = (state: State) => selectState(state).commentById || {}; +export const selectIsFetchingComments = (state: State) => selectState(state).isLoading; +export const selectIsFetchingCommentsById = (state: State) => selectState(state).isLoadingById; +export const selectIsFetchingCommentsByParentId = (state: State) => selectState(state).isLoadingByParentId; +export const selectIsFetchingReacts = (state: State) => selectState(state).isFetchingReacts; -export const selectPinnedCommentsById = createSelector(selectState, (state) => state.pinnedCommentsById); +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 = (state: State) => selectState(state).pinnedCommentsById; export const makeSelectPinnedCommentsForUri = (uri: string) => createSelector( selectCommentsByUri, @@ -36,8 +48,11 @@ export const makeSelectPinnedCommentsForUri = (uri: string) => } ); -export const selectModerationBlockList = createSelector(selectState, (state) => - state.moderationBlockList ? state.moderationBlockList.reverse() : [] +export const selectModerationBlockList = createSelector( + (state) => selectState(state).moderationBlockList, + (moderationBlockList) => { + return moderationBlockList ? moderationBlockList.reverse() : []; + } ); export const selectAdminBlockList = createSelector(selectState, (state) => state.adminBlockList ? state.adminBlockList.reverse() : [] @@ -46,35 +61,19 @@ export const selectModeratorBlockList = createSelector(selectState, (state) => state.moderatorBlockList ? state.moderatorBlockList.reverse() : [] ); -export const selectPersonalTimeoutMap = createSelector(selectState, (state) => state.personalTimeoutMap); -export const selectAdminTimeoutMap = createSelector(selectState, (state) => state.adminTimeoutMap); -export const selectModeratorTimeoutMap = createSelector(selectState, (state) => state.moderatorTimeoutMap); - -export const selectModeratorBlockListDelegatorsMap = createSelector( - selectState, - (state) => state.moderatorBlockListDelegatorsMap -); - -export const selectTogglingForDelegatorMap = createSelector(selectState, (state) => state.togglingForDelegatorMap); - -export const selectBlockingByUri = createSelector(selectState, (state) => state.blockingByUri); -export const selectUnBlockingByUri = createSelector(selectState, (state) => state.unBlockingByUri); -export const selectFetchingModerationBlockList = createSelector( - selectState, - (state) => state.fetchingModerationBlockList -); - -export const selectModerationDelegatesById = createSelector(selectState, (state) => state.moderationDelegatesById); -export const selectIsFetchingModerationDelegates = createSelector( - selectState, - (state) => state.fetchingModerationDelegates -); - -export const selectModerationDelegatorsById = createSelector(selectState, (state) => state.moderationDelegatorsById); -export const selectIsFetchingModerationDelegators = createSelector( - selectState, - (state) => state.fetchingModerationDelegators -); +export const selectPersonalTimeoutMap = (state: State) => selectState(state).personalTimeoutMap; +export const selectAdminTimeoutMap = (state: State) => selectState(state).adminTimeoutMap; +export const selectModeratorTimeoutMap = (state: State) => selectState(state).moderatorTimeoutMap; +export const selectModeratorBlockListDelegatorsMap = (state: State) => + selectState(state).moderatorBlockListDelegatorsMap; +export const selectTogglingForDelegatorMap = (state: State) => selectState(state).togglingForDelegatorMap; +export const selectBlockingByUri = (state: State) => selectState(state).blockingByUri; +export const selectUnBlockingByUri = (state: State) => selectState(state).unBlockingByUri; +export const selectFetchingModerationBlockList = (state: State) => selectState(state).fetchingModerationBlockList; +export const selectModerationDelegatesById = (state: State) => selectState(state).moderationDelegatesById; +export const selectIsFetchingModerationDelegates = (state: State) => selectState(state).fetchingModerationDelegates; +export const selectModerationDelegatorsById = (state: State) => selectState(state).moderationDelegatorsById; +export const selectIsFetchingModerationDelegators = (state: State) => selectState(state).fetchingModerationDelegators; export const selectHasAdminChannel = createSelector(selectState, (state) => { const myChannelIds = Object.keys(state.moderationDelegatorsById); @@ -107,7 +106,8 @@ export const selectCommentsByClaimId = createSelector(selectState, selectComment return comments; }); -export const selectSuperchatsByUri = createSelector(selectState, (state) => state.superChatsByUri); +// no superchats? +export const selectSuperchatsByUri = (state: State) => selectState(state).superChatsByUri; export const selectTopLevelCommentsByClaimId = createSelector(selectState, selectCommentsById, (state, byId) => { const byClaimId = state.topLevelCommentsById || {}; @@ -166,7 +166,7 @@ export const selectCommentsByUri = createSelector(selectState, (state) => { return comments; }); -export const selectLinkedCommentAncestors = createSelector(selectState, (state) => state.linkedCommentAncestors); +export const selectLinkedCommentAncestors = (state: State) => selectState(state).linkedCommentAncestors; export const makeSelectCommentIdsForUri = (uri: string) => createSelector(selectState, selectCommentsByUri, selectClaimsById, (state, byUri) => { @@ -174,38 +174,10 @@ 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); - -export const selectFetchingCreatorSettings = createSelector(selectState, (state) => state.fetchingSettings); - -export const selectFetchingBlockedWords = createSelector(selectState, (state) => state.fetchingBlockedWords); +export const selectPendingCommentReacts = (state: State) => selectState(state).pendingCommentReactions; +export const selectSettingsByChannelId = (state: State) => selectState(state).settingsByChannelId; +export const selectFetchingCreatorSettings = (state: State) => selectState(state).fetchingSettings; +export const selectFetchingBlockedWords = (state: State) => selectState(state).fetchingBlockedWords; export const makeSelectCommentsForUri = (uri: string) => createSelector( From 4d01452447317be087976096f59a48a034f86af0 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Thu, 7 Oct 2021 02:40:40 +0800 Subject: [PATCH 4/5] Fix reaction-selector reference invalidation When comments are refreshed, each `Comment` gets rendered 4-5 times due to reference invalidation for `othersReacts` (the data didn't actually 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/commentsList/index.js | 8 ++++---- ui/redux/actions/comments.js | 8 ++++---- 4 files changed, 13 insertions(+), 13 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/commentsList/index.js b/ui/component/commentsList/index.js index d32dc8476..a8a3962d0 100644 --- a/ui/component/commentsList/index.js +++ b/ui/component/commentsList/index.js @@ -12,8 +12,8 @@ import { selectIsFetchingCommentsById, selectIsFetchingReacts, makeSelectTotalCommentsCountForUri, - selectOthersReactsById, - selectMyReactionsByCommentId, + selectOthersReacts, + selectMyReacts, makeSelectCommentIdsForUri, selectSettingsByChannelId, makeSelectPinnedCommentsForUri, @@ -38,8 +38,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 d93cb6b52..5ab158fa1 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) { From 0459148e303dee9e9887c533abcff946cb35da6b Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Mon, 11 Oct 2021 14:02:17 +0800 Subject: [PATCH 5/5] Comment-selectors: fix memoization --- .../channelMentionSuggestions/index.js | 4 +- ui/component/commentsList/index.js | 18 +- ui/component/commentsReplies/index.js | 32 +- ui/page/ownComments/index.js | 4 +- ui/redux/selectors/comments.js | 321 +++++++++--------- 5 files changed, 207 insertions(+), 172 deletions(-) diff --git a/ui/component/channelMentionSuggestions/index.js b/ui/component/channelMentionSuggestions/index.js index d6b4af95b..c1f635011 100644 --- a/ui/component/channelMentionSuggestions/index.js +++ b/ui/component/channelMentionSuggestions/index.js @@ -4,12 +4,12 @@ import { selectSubscriptions } from 'redux/selectors/subscriptions'; import { withRouter } from 'react-router'; import { makeSelectClaimForUri } from 'redux/selectors/claims'; import { doResolveUris } from 'redux/actions/claims'; -import { makeSelectTopLevelCommentsForUri } from 'redux/selectors/comments'; +import { selectTopLevelCommentsForUri } from 'redux/selectors/comments'; import ChannelMentionSuggestions from './view'; const select = (state, props) => { const subscriptionUris = selectSubscriptions(state).map(({ uri }) => uri); - const topLevelComments = makeSelectTopLevelCommentsForUri(props.uri)(state); + const topLevelComments = selectTopLevelCommentsForUri(state, props.uri); const commentorUris = []; // Avoid repeated commentors diff --git a/ui/component/commentsList/index.js b/ui/component/commentsList/index.js index a8a3962d0..4b09b9b4f 100644 --- a/ui/component/commentsList/index.js +++ b/ui/component/commentsList/index.js @@ -1,4 +1,5 @@ import { connect } from 'react-redux'; +import { doResolveUris } from 'redux/actions/claims'; import { makeSelectClaimForUri, makeSelectClaimIsMine, @@ -6,7 +7,7 @@ import { selectMyChannelClaims, } from 'redux/selectors/claims'; import { - makeSelectTopLevelCommentsForUri, + selectTopLevelCommentsForUri, makeSelectTopLevelTotalPagesForUri, selectIsFetchingComments, selectIsFetchingCommentsById, @@ -16,7 +17,7 @@ import { selectMyReacts, makeSelectCommentIdsForUri, selectSettingsByChannelId, - makeSelectPinnedCommentsForUri, + selectPinnedCommentsForUri, } from 'redux/selectors/comments'; import { doCommentReset, doCommentList, doCommentById, doCommentReactList } from 'redux/actions/comments'; import { selectActiveChannelClaim } from 'redux/selectors/app'; @@ -24,11 +25,19 @@ import CommentsList from './view'; const select = (state, props) => { const activeChannelClaim = selectActiveChannelClaim(state); + const topLevelComments = selectTopLevelCommentsForUri(state, props.uri); + + const resolvedComments = + topLevelComments && topLevelComments.length > 0 + ? topLevelComments.filter(({ channel_url }) => makeSelectClaimForUri(channel_url)(state) !== undefined) + : []; + return { + topLevelComments, + resolvedComments, myChannels: selectMyChannelClaims(state), allCommentIds: makeSelectCommentIdsForUri(props.uri)(state), - pinnedComments: makeSelectPinnedCommentsForUri(props.uri)(state), - topLevelComments: makeSelectTopLevelCommentsForUri(props.uri)(state), + pinnedComments: selectPinnedCommentsForUri(state, props.uri), topLevelTotalPages: makeSelectTopLevelTotalPagesForUri(props.uri)(state), totalComments: makeSelectTotalCommentsCountForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state), @@ -49,6 +58,7 @@ const perform = (dispatch) => ({ fetchComment: (commentId) => dispatch(doCommentById(commentId)), fetchReacts: (commentIds) => dispatch(doCommentReactList(commentIds)), resetComments: (claimId) => dispatch(doCommentReset(claimId)), + doResolveUris: (uris) => dispatch(doResolveUris(uris, true)), }); export default connect(select, perform)(CommentsList); diff --git a/ui/component/commentsReplies/index.js b/ui/component/commentsReplies/index.js index 2b4a38c20..d7ac34b2e 100644 --- a/ui/component/commentsReplies/index.js +++ b/ui/component/commentsReplies/index.js @@ -1,15 +1,27 @@ import { connect } from 'react-redux'; -import { makeSelectClaimIsMine, selectMyChannelClaims } from 'redux/selectors/claims'; -import { selectIsFetchingCommentsByParentId, makeSelectRepliesForParentId } from 'redux/selectors/comments'; +import { doResolveUris } from 'redux/actions/claims'; +import { makeSelectClaimIsMine, selectMyChannelClaims, makeSelectClaimForUri } from 'redux/selectors/claims'; +import { selectIsFetchingCommentsByParentId, selectRepliesForParentId } from 'redux/selectors/comments'; import { selectUserVerifiedEmail } from 'redux/selectors/user'; import CommentsReplies from './view'; -const select = (state, props) => ({ - fetchedReplies: makeSelectRepliesForParentId(props.parentId)(state), - claimIsMine: makeSelectClaimIsMine(props.uri)(state), - commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true, - myChannels: selectMyChannelClaims(state), - isFetchingByParentId: selectIsFetchingCommentsByParentId(state), -}); +const select = (state, props) => { + const fetchedReplies = selectRepliesForParentId(state, props.parentId); + const resolvedReplies = + fetchedReplies && fetchedReplies.length > 0 + ? fetchedReplies.filter(({ channel_url }) => makeSelectClaimForUri(channel_url)(state) !== undefined) + : []; -export default connect(select)(CommentsReplies); + return { + fetchedReplies, + resolvedReplies, + claimIsMine: makeSelectClaimIsMine(props.uri)(state), + userCanComment: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true, + myChannels: selectMyChannelClaims(state), + isFetchingByParentId: selectIsFetchingCommentsByParentId(state), + }; +}; + +const perform = (dispatch) => ({ doResolveUris: (uris) => dispatch(doResolveUris(uris, true)) }); + +export default connect(select, perform)(CommentsReplies); diff --git a/ui/page/ownComments/index.js b/ui/page/ownComments/index.js index df53ecfa8..4c41717cb 100644 --- a/ui/page/ownComments/index.js +++ b/ui/page/ownComments/index.js @@ -3,7 +3,7 @@ import { doCommentListOwn, doCommentReset } from 'redux/actions/comments'; import { selectActiveChannelClaim } from 'redux/selectors/app'; import { selectIsFetchingComments, - makeSelectCommentsForUri, + selectCommentsForUri, makeSelectTotalCommentsCountForUri, makeSelectTopLevelTotalPagesForUri, } from 'redux/selectors/comments'; @@ -17,7 +17,7 @@ const select = (state) => { return { activeChannelClaim, - allComments: makeSelectCommentsForUri(uri)(state), + allComments: selectCommentsForUri(state, uri), totalComments: makeSelectTotalCommentsCountForUri(uri)(state), topLevelTotalPages: makeSelectTopLevelTotalPagesForUri(uri)(state), isFetchingComments: selectIsFetchingComments(state), diff --git a/ui/redux/selectors/comments.js b/ui/redux/selectors/comments.js index f71eecdb8..bc53eb785 100644 --- a/ui/redux/selectors/comments.js +++ b/ui/redux/selectors/comments.js @@ -1,5 +1,6 @@ // @flow import { createSelector } from 'reselect'; +import { createCachedSelector } from 're-reselect'; import { selectMutedChannels } from 'redux/selectors/blocked'; import { selectShowMatureContent } from 'redux/selectors/settings'; import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc'; @@ -27,26 +28,46 @@ export const selectOthersReactsForComment = (state: State, id: string) => { return state.comments.othersReactsByCommentId && state.comments.othersReactsByCommentId[id]; }; -export const selectPinnedCommentsById = (state: State) => selectState(state).pinnedCommentsById; -export const makeSelectPinnedCommentsForUri = (uri: string) => - createSelector( - selectCommentsByUri, - selectCommentsById, - selectPinnedCommentsById, - (byUri, byId, pinnedCommentsById) => { - const claimId = byUri[uri]; - const pinnedCommentIds = pinnedCommentsById && pinnedCommentsById[claimId]; - const pinnedComments = []; - - if (pinnedCommentIds) { - pinnedCommentIds.forEach((commentId) => { - pinnedComments.push(byId[commentId]); - }); - } - - return pinnedComments; +// previously this used a mapping from claimId -> Array +/* export const selectCommentsById = createSelector( + selectState, + state => state.byId || {} + ); */ +export const selectCommentsByUri = createSelector(selectState, (state) => { + const byUri = state.commentsByUri || {}; + const comments = {}; + Object.keys(byUri).forEach((uri) => { + const claimId = byUri[uri]; + if (claimId === null) { + comments[uri] = null; + } else { + comments[uri] = claimId; } - ); + }); + + return comments; +}); + +export const selectPinnedCommentsById = (state: State) => selectState(state).pinnedCommentsById; +export const selectPinnedCommentsForUri = createCachedSelector( + selectCommentsByUri, + selectCommentsById, + selectPinnedCommentsById, + (state, uri) => uri, + (byUri, byId, pinnedCommentsById, uri) => { + const claimId = byUri[uri]; + const pinnedCommentIds = pinnedCommentsById && pinnedCommentsById[claimId]; + const pinnedComments = []; + + if (pinnedCommentIds) { + pinnedCommentIds.forEach((commentId) => { + pinnedComments.push(byId[commentId]); + }); + } + + return pinnedComments; + } +)((state, uri) => uri); export const selectModerationBlockList = createSelector( (state) => selectState(state).moderationBlockList, @@ -109,22 +130,26 @@ export const selectCommentsByClaimId = createSelector(selectState, selectComment // no superchats? export const selectSuperchatsByUri = (state: State) => selectState(state).superChatsByUri; -export const selectTopLevelCommentsByClaimId = createSelector(selectState, selectCommentsById, (state, byId) => { - const byClaimId = state.topLevelCommentsById || {}; - const comments = {}; +export const selectTopLevelCommentsByClaimId = createSelector( + (state) => selectState(state).topLevelCommentsById, + selectCommentsById, + (topLevelCommentsById, byId) => { + const byClaimId = topLevelCommentsById || {}; + const comments = {}; - // replace every comment_id in the list with the actual comment object - Object.keys(byClaimId).forEach((claimId) => { - const commentIds = byClaimId[claimId]; + // replace every comment_id in the list with the actual comment object + Object.keys(byClaimId).forEach((claimId) => { + const commentIds = byClaimId[claimId]; - comments[claimId] = Array(commentIds === null ? 0 : commentIds.length); - for (let i = 0; i < commentIds.length; i++) { - comments[claimId][i] = byId[commentIds[i]]; - } - }); + comments[claimId] = Array(commentIds === null ? 0 : commentIds.length); + for (let i = 0; i < commentIds.length; i++) { + comments[claimId][i] = byId[commentIds[i]]; + } + }); - return comments; -}); + return comments; + } +); export const makeSelectCommentForCommentId = (commentId: string) => createSelector(selectCommentsById, (comments) => comments[commentId]); @@ -146,26 +171,6 @@ export const selectRepliesByParentId = createSelector(selectState, selectComment return comments; }); -// previously this used a mapping from claimId -> Array -/* export const selectCommentsById = createSelector( - selectState, - state => state.byId || {} -); */ -export const selectCommentsByUri = createSelector(selectState, (state) => { - const byUri = state.commentsByUri || {}; - const comments = {}; - Object.keys(byUri).forEach((uri) => { - const claimId = byUri[uri]; - if (claimId === null) { - comments[uri] = null; - } else { - comments[uri] = claimId; - } - }); - - return comments; -}); - export const selectLinkedCommentAncestors = (state: State) => selectState(state).linkedCommentAncestors; export const makeSelectCommentIdsForUri = (uri: string) => @@ -174,34 +179,48 @@ export const makeSelectCommentIdsForUri = (uri: string) => return state.byId[claimId]; }); +const filterCommentsDepOnList = { + claimsById: selectClaimsById, + myClaims: selectMyActiveClaims, + mutedChannels: selectMutedChannels, + personalBlockList: selectModerationBlockList, + blacklistedMap: selectBlacklistedOutpointMap, + filteredMap: selectFilteredOutpointMap, + showMatureContent: selectShowMatureContent, +}; + +const filterCommentsPropKeys = Object.keys(filterCommentsDepOnList); + export const selectPendingCommentReacts = (state: State) => selectState(state).pendingCommentReactions; export const selectSettingsByChannelId = (state: State) => selectState(state).settingsByChannelId; export const selectFetchingCreatorSettings = (state: State) => selectState(state).fetchingSettings; export const selectFetchingBlockedWords = (state: State) => selectState(state).fetchingBlockedWords; -export const makeSelectCommentsForUri = (uri: string) => - createSelector( - (state) => state, - selectCommentsByClaimId, - selectCommentsByUri, - (state, byClaimId, byUri) => { - const claimId = byUri[uri]; - const comments = byClaimId && byClaimId[claimId]; - return makeSelectFilteredComments(comments, claimId)(state); - } - ); +export const selectCommentsForUri = createCachedSelector( + (state, uri) => uri, + selectCommentsByClaimId, + selectCommentsByUri, + ...Object.values(filterCommentsDepOnList), + (uri, byClaimId, byUri, ...filterInputs) => { + const claimId = byUri[uri]; + const comments = byClaimId && byClaimId[claimId]; + return filterComments(comments, claimId, filterInputs); + } +)((state, uri) => uri); -export const makeSelectTopLevelCommentsForUri = (uri: string) => - createSelector( - (state) => state, - selectTopLevelCommentsByClaimId, - selectCommentsByUri, - (state, byClaimId, byUri) => { - const claimId = byUri[uri]; - const comments = byClaimId && byClaimId[claimId]; - return makeSelectFilteredComments(comments, claimId)(state); - } - ); +export const selectTopLevelCommentsForUri = createCachedSelector( + (state, uri) => uri, + (state, uri, maxCount) => maxCount, + selectTopLevelCommentsByClaimId, + selectCommentsByUri, + ...Object.values(filterCommentsDepOnList), + (uri, maxCount = -1, byClaimId, byUri, ...filterInputs) => { + const claimId = byUri[uri]; + const comments = byClaimId && byClaimId[claimId]; + const filtered = filterComments(comments, claimId, filterInputs); + return maxCount > 0 ? filtered.slice(0, maxCount) : filtered; + } +)((state, uri, maxCount = -1) => `${uri}:${maxCount}`); export const makeSelectTopLevelTotalCommentsForUri = (uri: string) => createSelector(selectState, selectCommentsByUri, (state, byUri) => { @@ -215,99 +234,93 @@ export const makeSelectTopLevelTotalPagesForUri = (uri: string) => return state.topLevelTotalPagesById[claimId] || 0; }); -export const makeSelectRepliesForParentId = (id: string) => - createSelector( - (state) => state, - selectCommentsById, - (state, commentsById) => { - // const claimId = byUri[uri]; // just parentId (id) - const replyIdsByParentId = state.comments.repliesByParentId; - const replyIdsForParent = replyIdsByParentId[id] || []; - if (!replyIdsForParent.length) return null; +export const selectRepliesForParentId = createCachedSelector( + (state, id) => id, + (state) => selectState(state).repliesByParentId, + selectCommentsById, + ...Object.values(filterCommentsDepOnList), + (id, repliesByParentId, commentsById, ...filterInputs) => { + // const claimId = byUri[uri]; // just parentId (id) + const replyIdsForParent = repliesByParentId[id] || []; + if (!replyIdsForParent.length) return null; - const comments = []; - replyIdsForParent.forEach((cid) => { - comments.push(commentsById[cid]); - }); - // const comments = byParentId && byParentId[id]; + const comments = []; + replyIdsForParent.forEach((cid) => { + comments.push(commentsById[cid]); + }); + // const comments = byParentId && byParentId[id]; - return makeSelectFilteredComments(comments)(state); - } - ); + return filterComments(comments, undefined, filterInputs); + } +)((state, id: string) => id); /** - * makeSelectFilteredComments + * filterComments * * @param comments List of comments to filter. * @param claimId The claim that `comments` reside in. + * @oaram filterInputs Values returned by filterCommentsDepOnList. */ -const makeSelectFilteredComments = (comments: Array, claimId?: string) => - createSelector( - selectClaimsById, - selectMyActiveClaims, - selectMutedChannels, - selectModerationBlockList, - selectAdminBlockList, - selectModeratorBlockList, - selectBlacklistedOutpointMap, - selectFilteredOutpointMap, - selectShowMatureContent, - ( - claimsById, - myClaims, - mutedChannels, - personalBlockList, - adminBlockList, - moderatorBlockList, - blacklistedMap, - filteredMap, - showMatureContent - ) => { - return comments - ? comments.filter((comment) => { - if (!comment) { - // It may have been recently deleted after being blocked +const filterComments = (comments: Array, claimId?: string, filterInputs: any) => { + const filterProps = filterInputs.reduce(function (acc, cur, i) { + acc[filterCommentsPropKeys[i]] = cur; + return acc; + }, {}); + + const { + claimsById, + myClaims, + mutedChannels, + personalBlockList, + blacklistedMap, + filteredMap, + showMatureContent, + } = filterProps; + + return comments + ? comments.filter((comment) => { + if (!comment) { + // It may have been recently deleted after being blocked + return false; + } + + const channelClaim = claimsById[comment.channel_id]; + + // Return comment if `channelClaim` doesn't exist so the component knows to resolve the author + if (channelClaim) { + if (myClaims && myClaims.size > 0) { + const claimIsMine = channelClaim.is_my_output || myClaims.has(channelClaim.claim_id); + if (claimIsMine) { + return true; + } + } + + const outpoint = `${channelClaim.txid}:${channelClaim.nout}`; + if (blacklistedMap[outpoint] || filteredMap[outpoint]) { + return false; + } + + if (!showMatureContent) { + const claimIsMature = isClaimNsfw(channelClaim); + if (claimIsMature) { return false; } + } + } - const channelClaim = claimsById[comment.channel_id]; - - // Return comment if `channelClaim` doesn't exist so the component knows to resolve the author - if (channelClaim) { - if (myClaims && myClaims.size > 0) { - const claimIsMine = channelClaim.is_my_output || myClaims.has(channelClaim.claim_id); - if (claimIsMine) { - return true; - } - } - - const outpoint = `${channelClaim.txid}:${channelClaim.nout}`; - if (blacklistedMap[outpoint] || filteredMap[outpoint]) { - return false; - } - - if (!showMatureContent) { - const claimIsMature = isClaimNsfw(channelClaim); - if (claimIsMature) { - return false; - } - } + if (claimId) { + const claimIdIsMine = myClaims && myClaims.size > 0 && myClaims.has(claimId); + if (!claimIdIsMine) { + if (personalBlockList.includes(comment.channel_url)) { + return false; } + } + } - if (claimId) { - const claimIdIsMine = myClaims && myClaims.size > 0 && myClaims.has(claimId); - if (!claimIdIsMine) { - if (personalBlockList.includes(comment.channel_url) || adminBlockList.includes(comment.channel_url)) { - return false; - } - } - } - - return !mutedChannels.includes(comment.channel_url); - }) - : []; - } - ); + return !mutedChannels.includes(comment.channel_url); + }) + : []; +}; export const makeSelectTotalReplyPagesForParentId = (parentId: string) => createSelector(selectState, (state) => {