Fix and optimize makeSelectIsSubscribed (#273)

## Issues with `makeSelectIsSubscribed`
- It will not return true if the uri provided is canonical, because the compared subscription uri is in permanent form. This was causing certain elements like the Heart to not appear in claim tiles.
- It is super slow for large subscriptions not just because of the array size + being a hot selector, but also because it is looking up the claim twice (not memo'd) and also calling `parseURI` to determine if it's a channel, which is unnecessary if you already have the claim.

## Changes
- Optimize the selector to only look up the claim once, and make operations using already-obtained info.
This commit is contained in:
infinite-persistence 2021-11-12 22:47:07 +08:00 committed by GitHub
parent 53406a60cf
commit 6f8758c819
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 31 additions and 44 deletions

View file

@ -26,7 +26,7 @@ import {
} from 'redux/selectors/comments'; } from 'redux/selectors/comments';
import { doToast } from 'redux/actions/notifications'; import { doToast } from 'redux/actions/notifications';
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions'; import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions'; import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
import { selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectListShuffle } from 'redux/selectors/content'; import { selectListShuffle } from 'redux/selectors/content';
import { doToggleLoopList, doToggleShuffleList } from 'redux/actions/content'; import { doToggleLoopList, doToggleShuffleList } from 'redux/actions/content';
@ -63,7 +63,7 @@ const select = (state, props) => {
channelIsMuted: makeSelectChannelIsMuted(contentChannelUri)(state), channelIsMuted: makeSelectChannelIsMuted(contentChannelUri)(state),
channelIsBlocked: makeSelectChannelIsBlocked(contentChannelUri)(state), channelIsBlocked: makeSelectChannelIsBlocked(contentChannelUri)(state),
fileInfo: makeSelectFileInfoForUri(contentPermanentUri)(state), fileInfo: makeSelectFileInfoForUri(contentPermanentUri)(state),
isSubscribed: makeSelectIsSubscribed(contentChannelUri, true)(state), isSubscribed: selectIsSubscribedForUri(state, contentChannelUri),
channelIsAdminBlocked: makeSelectChannelIsAdminBlocked(props.uri)(state), channelIsAdminBlocked: makeSelectChannelIsAdminBlocked(props.uri)(state),
isAdmin: selectHasAdminChannel(state), isAdmin: selectHasAdminChannel(state),
claimInCollection: makeSelectCollectionForIdHasClaimUrl(collectionId, contentPermanentUri)(state), claimInCollection: makeSelectCollectionForIdHasClaimUrl(collectionId, contentPermanentUri)(state),

View file

@ -25,7 +25,7 @@ import { selectBanStateForUri } from 'lbryinc';
import { makeSelectIsActiveLivestream } from 'redux/selectors/livestream'; import { makeSelectIsActiveLivestream } from 'redux/selectors/livestream';
import { selectShowMatureContent } from 'redux/selectors/settings'; import { selectShowMatureContent } from 'redux/selectors/settings';
import { makeSelectHasVisitedUri } from 'redux/selectors/content'; import { makeSelectHasVisitedUri } from 'redux/selectors/content';
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions'; import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
import ClaimPreview from './view'; import ClaimPreview from './view';
import formatMediaDuration from 'util/formatMediaDuration'; import formatMediaDuration from 'util/formatMediaDuration';
@ -48,7 +48,7 @@ const select = (state, props) => {
nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state), nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state),
banState: selectBanStateForUri(state, props.uri), banState: selectBanStateForUri(state, props.uri),
hasVisitedUri: props.uri && makeSelectHasVisitedUri(props.uri)(state), hasVisitedUri: props.uri && makeSelectHasVisitedUri(props.uri)(state),
isSubscribed: props.uri && makeSelectIsSubscribed(props.uri, true)(state), isSubscribed: props.uri && selectIsSubscribedForUri(state, props.uri),
streamingUrl: props.uri && makeSelectStreamingUrlForUri(props.uri)(state), streamingUrl: props.uri && makeSelectStreamingUrlForUri(props.uri)(state),
wasPurchased: props.uri && makeSelectClaimWasPurchased(props.uri)(state), wasPurchased: props.uri && makeSelectClaimWasPurchased(props.uri)(state),
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state), isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectClaimIsMine, selectClaimForUri } from 'redux/selectors/claims'; import { selectClaimIsMine, selectClaimForUri } from 'redux/selectors/claims';
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions'; import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
import ClaimProperties from './view'; import ClaimProperties from './view';
const select = (state, props) => { const select = (state, props) => {
@ -8,7 +8,7 @@ const select = (state, props) => {
return { return {
claim, claim,
isSubscribed: makeSelectIsSubscribed(props.uri)(state), isSubscribed: selectIsSubscribedForUri(state, props.uri),
claimIsMine: selectClaimIsMine(state, claim), claimIsMine: selectClaimIsMine(state, claim),
}; };
}; };

View file

@ -5,7 +5,7 @@ import { selectUser, selectSetReferrerPending, selectSetReferrerError } from 're
import { doClaimRewardType } from 'redux/actions/rewards'; import { doClaimRewardType } from 'redux/actions/rewards';
import { selectUnclaimedRewards } from 'redux/selectors/rewards'; import { selectUnclaimedRewards } from 'redux/selectors/rewards';
import { doUserSetReferrer } from 'redux/actions/user'; import { doUserSetReferrer } from 'redux/actions/user';
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions'; import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
import { doChannelSubscribe } from 'redux/actions/subscriptions'; import { doChannelSubscribe } from 'redux/actions/subscriptions';
import Invited from './view'; import Invited from './view';
@ -15,16 +15,16 @@ const select = (state, props) => {
referrerSetPending: selectSetReferrerPending(state), referrerSetPending: selectSetReferrerPending(state),
referrerSetError: selectSetReferrerError(state), referrerSetError: selectSetReferrerError(state),
rewards: selectUnclaimedRewards(state), rewards: selectUnclaimedRewards(state),
isSubscribed: makeSelectIsSubscribed(props.fullUri)(state), isSubscribed: selectIsSubscribedForUri(state, props.fullUri),
fullUri: props.fullUri, fullUri: props.fullUri,
referrer: props.referrer, referrer: props.referrer,
}; };
}; };
const perform = dispatch => ({ const perform = (dispatch) => ({
claimReward: () => dispatch(doClaimRewardType(REWARDS.TYPE_REFEREE)), claimReward: () => dispatch(doClaimRewardType(REWARDS.TYPE_REFEREE)),
setReferrer: referrer => dispatch(doUserSetReferrer(referrer)), setReferrer: (referrer) => dispatch(doUserSetReferrer(referrer)),
channelSubscribe: uri => dispatch(doChannelSubscribe(uri)), channelSubscribe: (uri) => dispatch(doChannelSubscribe(uri)),
}); });
export default withRouter(connect(select, perform)(Invited)); export default withRouter(connect(select, perform)(Invited));

View file

@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import { selectClaimIsMine, selectClaimForUri } from 'redux/selectors/claims'; import { selectClaimIsMine, selectClaimForUri } from 'redux/selectors/claims';
import { makeSelectFilePartlyDownloaded } from 'redux/selectors/file_info'; import { makeSelectFilePartlyDownloaded } from 'redux/selectors/file_info';
import { makeSelectEditedCollectionForId } from 'redux/selectors/collections'; import { makeSelectEditedCollectionForId } from 'redux/selectors/collections';
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions'; import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
import PreviewOverlayProperties from './view'; import PreviewOverlayProperties from './view';
const select = (state, props) => { const select = (state, props) => {
@ -13,7 +13,7 @@ const select = (state, props) => {
claim, claim,
editedCollection: makeSelectEditedCollectionForId(claimId)(state), editedCollection: makeSelectEditedCollectionForId(claimId)(state),
downloaded: makeSelectFilePartlyDownloaded(props.uri)(state), downloaded: makeSelectFilePartlyDownloaded(props.uri)(state),
isSubscribed: makeSelectIsSubscribed(props.uri)(state), isSubscribed: selectIsSubscribedForUri(state, props.uri),
claimIsMine: selectClaimIsMine(state, claim), claimIsMine: selectClaimIsMine(state, claim),
}; };
}; };

View file

@ -1,7 +1,7 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions'; import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
import { import {
makeSelectIsSubscribed, selectIsSubscribedForUri,
selectFirstRunCompleted, selectFirstRunCompleted,
makeSelectNotificationsDisabled, makeSelectNotificationsDisabled,
} from 'redux/selectors/subscriptions'; } from 'redux/selectors/subscriptions';
@ -11,7 +11,7 @@ import { doToast } from 'redux/actions/notifications';
import SubscribeButton from './view'; import SubscribeButton from './view';
const select = (state, props) => ({ const select = (state, props) => ({
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state), isSubscribed: selectIsSubscribedForUri(state, props.uri),
firstRunCompleted: selectFirstRunCompleted(state), firstRunCompleted: selectFirstRunCompleted(state),
permanentUrl: makeSelectPermanentUrlForUri(props.uri)(state), permanentUrl: makeSelectPermanentUrlForUri(props.uri)(state),
notificationsDisabled: makeSelectNotificationsDisabled(props.uri)(state), notificationsDisabled: makeSelectNotificationsDisabled(props.uri)(state),

View file

@ -11,7 +11,7 @@ import {
import { selectMyUnpublishedCollections } from 'redux/selectors/collections'; import { selectMyUnpublishedCollections } from 'redux/selectors/collections';
import { selectBlackListedOutpoints, doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc'; import { selectBlackListedOutpoints, doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc';
import { selectYoutubeChannels } from 'redux/selectors/user'; import { selectYoutubeChannels } from 'redux/selectors/user';
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions'; import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
import { selectModerationBlockList } from 'redux/selectors/comments'; import { selectModerationBlockList } from 'redux/selectors/comments';
import { selectMutedChannels } from 'redux/selectors/blocked'; import { selectMutedChannels } from 'redux/selectors/blocked';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal } from 'redux/actions/app';
@ -27,7 +27,7 @@ const select = (state, props) => {
channelIsMine: selectClaimIsMine(state, claim), channelIsMine: selectClaimIsMine(state, claim),
page: selectCurrentChannelPage(state), page: selectCurrentChannelPage(state),
claim, claim,
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state), isSubscribed: selectIsSubscribedForUri(state, props.uri),
blackListedOutpoints: selectBlackListedOutpoints(state), blackListedOutpoints: selectBlackListedOutpoints(state),
subCount: makeSelectSubCountForUri(props.uri)(state), subCount: makeSelectSubCountForUri(props.uri)(state),
pending: makeSelectClaimIsPending(props.uri)(state), pending: makeSelectClaimIsPending(props.uri)(state),

View file

@ -1,12 +1,15 @@
import { SUGGESTED_FEATURED, SUGGESTED_TOP_SUBSCRIBED } from 'constants/subscriptions'; import { SUGGESTED_FEATURED, SUGGESTED_TOP_SUBSCRIBED } from 'constants/subscriptions';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { createCachedSelector } from 're-reselect';
import { parseURI, isURIEqual } from 'util/lbryURI'; import { parseURI, isURIEqual } from 'util/lbryURI';
import { import {
selectAllFetchingChannelClaims, selectAllFetchingChannelClaims,
makeSelectChannelForClaimUri, makeSelectChannelForClaimUri,
makeSelectClaimForUri, makeSelectClaimForUri,
selectClaimForUri,
} from 'redux/selectors/claims'; } from 'redux/selectors/claims';
import { swapKeyAndValue } from 'util/swap-json'; import { swapKeyAndValue } from 'util/swap-json';
import { getChannelFromClaim } from 'util/claim';
// Returns the entire subscriptions state // Returns the entire subscriptions state
const selectState = (state) => state.subscriptions || {}; const selectState = (state) => state.subscriptions || {};
@ -105,34 +108,18 @@ export const selectSubscriptionsBeingFetched = createSelector(
export const makeSelectChannelInSubscriptions = (uri) => export const makeSelectChannelInSubscriptions = (uri) =>
createSelector(selectSubscriptions, (subscriptions) => subscriptions.some((sub) => sub.uri === uri)); createSelector(selectSubscriptions, (subscriptions) => subscriptions.some((sub) => sub.uri === uri));
export const makeSelectIsSubscribed = (uri) => export const selectIsSubscribedForUri = createCachedSelector(
createSelector( selectClaimForUri,
selectSubscriptions, selectSubscriptions,
makeSelectChannelForClaimUri(uri, true), (claim, subscriptions) => {
makeSelectClaimForUri(uri), const channelClaim = getChannelFromClaim(claim);
(subscriptions, channelUri, claim) => { if (channelClaim) {
if (channelUri) { const uri = channelClaim.permanent_url;
return subscriptions.some((sub) => isURIEqual(sub.uri, channelUri));
}
// If we couldn't get a channel uri from the claim uri, the uri passed in might be a channel already
let isChannel;
try {
({ isChannel } = parseURI(uri));
} catch (e) {}
if (isChannel && claim) {
const uri = claim.permanent_url;
return subscriptions.some((sub) => isURIEqual(sub.uri, uri)); return subscriptions.some((sub) => isURIEqual(sub.uri, uri));
} }
if (isChannel && !claim) {
return subscriptions.some((sub) => isURIEqual(sub.uri, uri));
}
return false; return false;
} }
); )((state, uri) => String(uri));
export const makeSelectNotificationsDisabled = (uri) => export const makeSelectNotificationsDisabled = (uri) =>
createSelector( createSelector(