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:
parent
53406a60cf
commit
6f8758c819
8 changed files with 31 additions and 44 deletions
|
@ -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),
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in a new issue