Reduce impact of scanning blocklists (#121)
## Issue - Each tile was checking against 4 blocklists (blacklisted, filtered, muted, commentron) on every render. Loading the front-page with Cheese alone caused 1400 calls. - This is also part of the reason why pressing Back into the tile list takes forever. ## Fix Since we still need to perform the checks at the app side for now, tried to memoize the operation through a selector.
This commit is contained in:
parent
dad7264636
commit
a90c516c71
6 changed files with 117 additions and 122 deletions
|
@ -56,6 +56,7 @@ export { selectFilteredOutpoints, selectFilteredOutpointMap } from './redux/sele
|
||||||
// selectFetchingTrendingUris,
|
// selectFetchingTrendingUris,
|
||||||
// } from './redux/selectors/homepage';
|
// } from './redux/selectors/homepage';
|
||||||
export { selectViewCount, makeSelectViewCountForUri, makeSelectSubCountForUri } from './redux/selectors/stats';
|
export { selectViewCount, makeSelectViewCountForUri, makeSelectSubCountForUri } from './redux/selectors/stats';
|
||||||
|
export { selectBanStateForUri } from './redux/selectors/ban';
|
||||||
export {
|
export {
|
||||||
selectHasSyncedWallet,
|
selectHasSyncedWallet,
|
||||||
selectSyncData,
|
selectSyncData,
|
||||||
|
|
96
extras/lbryinc/redux/selectors/ban.js
Normal file
96
extras/lbryinc/redux/selectors/ban.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
// TODO: This should be in 'redux/selectors/claim.js'. Temporarily putting it
|
||||||
|
// here to get past importing issues with 'lbryinc', which the real fix might
|
||||||
|
// involve moving it from 'extras' to 'ui' (big change).
|
||||||
|
|
||||||
|
import { createCachedSelector } from 're-reselect';
|
||||||
|
import { selectClaimForUri } from 'redux/selectors/claims';
|
||||||
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
|
import { selectModerationBlockList } from 'redux/selectors/comments';
|
||||||
|
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||||
|
import { isURIEqual } from 'util/lbryURI';
|
||||||
|
|
||||||
|
const selectClaimExistsForUri = (state, uri) => {
|
||||||
|
return Boolean(selectClaimForUri(state, uri));
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectTxidForUri = (state, uri) => {
|
||||||
|
const claim = selectClaimForUri(state, uri);
|
||||||
|
const signingChannel = claim && claim.signing_channel;
|
||||||
|
return signingChannel ? signingChannel.txid : claim ? claim.txid : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectNoutForUri = (state, uri) => {
|
||||||
|
const claim = selectClaimForUri(state, uri);
|
||||||
|
const signingChannel = claim && claim.signing_channel;
|
||||||
|
return signingChannel ? signingChannel.nout : claim ? claim.nout : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectPermanentUrlForUri = (state, uri) => {
|
||||||
|
const claim = selectClaimForUri(state, uri);
|
||||||
|
const signingChannel = claim && claim.signing_channel;
|
||||||
|
return signingChannel ? signingChannel.permanent_url : claim ? claim.permanent_url : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectBanStateForUri = createCachedSelector(
|
||||||
|
// Break apart 'selectClaimForUri' into 4 cheaper selectors that return
|
||||||
|
// primitives for values that we care about. The Claim object itself is easily
|
||||||
|
// invalidated due to constantly-changing fields like 'confirmation'.
|
||||||
|
selectClaimExistsForUri,
|
||||||
|
selectTxidForUri,
|
||||||
|
selectNoutForUri,
|
||||||
|
selectPermanentUrlForUri,
|
||||||
|
selectBlackListedOutpoints,
|
||||||
|
selectFilteredOutpoints,
|
||||||
|
selectMutedChannels,
|
||||||
|
selectModerationBlockList,
|
||||||
|
(
|
||||||
|
claimExists,
|
||||||
|
txid,
|
||||||
|
nout,
|
||||||
|
permanentUrl,
|
||||||
|
blackListedOutpoints,
|
||||||
|
filteredOutpoints,
|
||||||
|
mutedChannelUris,
|
||||||
|
personalBlocklist
|
||||||
|
) => {
|
||||||
|
const banState = {};
|
||||||
|
|
||||||
|
if (!claimExists) {
|
||||||
|
return banState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will be replaced once blocking is done at the wallet server level.
|
||||||
|
if (blackListedOutpoints) {
|
||||||
|
if (blackListedOutpoints.some((outpoint) => outpoint.txid === txid && outpoint.nout === nout)) {
|
||||||
|
banState['blacklisted'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're checking to see if the stream outpoint or signing channel outpoint
|
||||||
|
// is in the filter list.
|
||||||
|
if (filteredOutpoints) {
|
||||||
|
if (filteredOutpoints.some((outpoint) => outpoint.txid === txid && outpoint.nout === nout)) {
|
||||||
|
banState['filtered'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// block stream claims
|
||||||
|
// block channel claims if we can't control for them in claim search
|
||||||
|
if (mutedChannelUris.length) {
|
||||||
|
if (mutedChannelUris.some((blockedUri) => isURIEqual(blockedUri, permanentUrl))) {
|
||||||
|
banState['muted'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commentron blocklist
|
||||||
|
if (personalBlocklist.length) {
|
||||||
|
if (personalBlocklist.some((blockedUri) => isURIEqual(blockedUri, permanentUrl))) {
|
||||||
|
banState['blocked'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return banState;
|
||||||
|
}
|
||||||
|
)((state, uri) => String(uri));
|
|
@ -21,13 +21,11 @@ import {
|
||||||
import { doResolveUri } from 'redux/actions/claims';
|
import { doResolveUri } from 'redux/actions/claims';
|
||||||
import { doCollectionEdit } from 'redux/actions/collections';
|
import { doCollectionEdit } from 'redux/actions/collections';
|
||||||
import { doFileGet } from 'redux/actions/file';
|
import { doFileGet } from 'redux/actions/file';
|
||||||
import { selectMutedChannels, makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
import { selectBanStateForUri } from 'lbryinc';
|
||||||
import { selectBlackListedOutpoints, selectFilteredOutpoints } 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 { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||||
import { selectModerationBlockList } from 'redux/selectors/comments';
|
|
||||||
import ClaimPreview from './view';
|
import ClaimPreview from './view';
|
||||||
import formatMediaDuration from 'util/formatMediaDuration';
|
import formatMediaDuration from 'util/formatMediaDuration';
|
||||||
|
|
||||||
|
@ -48,12 +46,8 @@ const select = (state, props) => {
|
||||||
isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state),
|
isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state),
|
||||||
isResolvingRepost: props.uri && makeSelectIsUriResolving(props.repostUrl)(state),
|
isResolvingRepost: props.uri && makeSelectIsUriResolving(props.repostUrl)(state),
|
||||||
nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state),
|
nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state),
|
||||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
banState: selectBanStateForUri(state, props.uri),
|
||||||
filteredOutpoints: selectFilteredOutpoints(state),
|
|
||||||
mutedUris: selectMutedChannels(state),
|
|
||||||
blockedUris: selectModerationBlockList(state),
|
|
||||||
hasVisitedUri: props.uri && makeSelectHasVisitedUri(props.uri)(state),
|
hasVisitedUri: props.uri && makeSelectHasVisitedUri(props.uri)(state),
|
||||||
channelIsBlocked: props.uri && makeSelectChannelIsMuted(props.uri)(state),
|
|
||||||
isSubscribed: props.uri && makeSelectIsSubscribed(props.uri, true)(state),
|
isSubscribed: props.uri && makeSelectIsSubscribed(props.uri, true)(state),
|
||||||
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),
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { NavLink, withRouter } from 'react-router-dom';
|
||||||
import { isEmpty } from 'util/object';
|
import { isEmpty } from 'util/object';
|
||||||
import { lazyImport } from 'util/lazyImport';
|
import { lazyImport } from 'util/lazyImport';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { isURIEqual, isURIValid } from 'util/lbryURI';
|
import { isURIValid } from 'util/lbryURI';
|
||||||
import * as COLLECTIONS_CONSTS from 'constants/collections';
|
import * as COLLECTIONS_CONSTS from 'constants/collections';
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
import { formatLbryUrlForWeb } from 'util/url';
|
||||||
import { formatClaimPreviewTitle } from 'util/formatAriaLabel';
|
import { formatClaimPreviewTitle } from 'util/formatAriaLabel';
|
||||||
|
@ -52,18 +52,9 @@ type Props = {
|
||||||
nsfw: boolean,
|
nsfw: boolean,
|
||||||
placeholder: string,
|
placeholder: string,
|
||||||
type: string,
|
type: string,
|
||||||
|
banState: { blacklisted?: boolean, filtered?: boolean, muted?: boolean, blocked?: boolean },
|
||||||
hasVisitedUri: boolean,
|
hasVisitedUri: boolean,
|
||||||
blackListedOutpoints: Array<{
|
|
||||||
txid: string,
|
|
||||||
nout: number,
|
|
||||||
}>,
|
|
||||||
filteredOutpoints: Array<{
|
|
||||||
txid: string,
|
|
||||||
nout: number,
|
|
||||||
}>,
|
|
||||||
mutedUris: Array<string>,
|
|
||||||
blockedUris: Array<string>,
|
blockedUris: Array<string>,
|
||||||
channelIsBlocked: boolean,
|
|
||||||
actions: boolean | Node | string | number,
|
actions: boolean | Node | string | number,
|
||||||
properties: boolean | Node | string | number | ((Claim) => Node),
|
properties: boolean | Node | string | number | ((Claim) => Node),
|
||||||
empty?: Node,
|
empty?: Node,
|
||||||
|
@ -113,7 +104,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
streamingUrl,
|
streamingUrl,
|
||||||
mediaDuration,
|
mediaDuration,
|
||||||
// user properties
|
// user properties
|
||||||
channelIsBlocked,
|
|
||||||
hasVisitedUri,
|
hasVisitedUri,
|
||||||
// component
|
// component
|
||||||
history,
|
history,
|
||||||
|
@ -139,10 +129,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
properties,
|
properties,
|
||||||
onClick,
|
onClick,
|
||||||
actions,
|
actions,
|
||||||
mutedUris,
|
banState,
|
||||||
blockedUris,
|
|
||||||
blackListedOutpoints,
|
|
||||||
filteredOutpoints,
|
|
||||||
includeSupportAction,
|
includeSupportAction,
|
||||||
renderActions,
|
renderActions,
|
||||||
hideMenu = false,
|
hideMenu = false,
|
||||||
|
@ -246,28 +233,13 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
((abandoned && !showUnresolvedClaim) || (!claimIsMine && obscureNsfw && nsfw));
|
((abandoned && !showUnresolvedClaim) || (!claimIsMine && obscureNsfw && nsfw));
|
||||||
|
|
||||||
// This will be replaced once blocking is done at the wallet server level
|
// This will be replaced once blocking is done at the wallet server level
|
||||||
if (claim && !claimIsMine && !shouldHide && blackListedOutpoints) {
|
if (!shouldHide && !claimIsMine && (banState.blacklisted || banState.filtered)) {
|
||||||
shouldHide = blackListedOutpoints.some(
|
shouldHide = true;
|
||||||
(outpoint) =>
|
|
||||||
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
|
|
||||||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// We're checking to see if the stream outpoint
|
|
||||||
// or signing channel outpoint is in the filter list
|
|
||||||
if (claim && !claimIsMine && !shouldHide && filteredOutpoints) {
|
|
||||||
shouldHide = filteredOutpoints.some(
|
|
||||||
(outpoint) =>
|
|
||||||
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
|
|
||||||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// block stream claims
|
// block stream claims
|
||||||
if (claim && !shouldHide && !showUserBlocked && mutedUris.length && signingChannel) {
|
if (!shouldHide && !showUserBlocked && (banState.muted || banState.blocked)) {
|
||||||
shouldHide = mutedUris.some((blockedUri) => isURIEqual(blockedUri, signingChannel.permanent_url));
|
shouldHide = true;
|
||||||
}
|
|
||||||
if (claim && !shouldHide && !showUserBlocked && blockedUris.length && signingChannel) {
|
|
||||||
shouldHide = blockedUris.some((blockedUri) => isURIEqual(blockedUri, signingChannel.permanent_url));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldHide && customShouldHide && claim) {
|
if (!shouldHide && customShouldHide && claim) {
|
||||||
|
@ -482,7 +454,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isChannelUri && !channelIsBlocked && !claimIsMine && (
|
{isChannelUri && !banState.muted && !claimIsMine && (
|
||||||
<SubscribeButton
|
<SubscribeButton
|
||||||
uri={repostedChannelUri || (uri.startsWith('lbry://') ? uri : `lbry://${uri}`)}
|
uri={repostedChannelUri || (uri.startsWith('lbry://') ? uri : `lbry://${uri}`)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -11,8 +11,7 @@ import {
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
import { doFileGet } from 'redux/actions/file';
|
import { doFileGet } from 'redux/actions/file';
|
||||||
import { doResolveUri } from 'redux/actions/claims';
|
import { doResolveUri } from 'redux/actions/claims';
|
||||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
import { makeSelectViewCountForUri, selectBanStateForUri } from 'lbryinc';
|
||||||
import { makeSelectViewCountForUri, selectBlackListedOutpoints, selectFilteredOutpoints } 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 ClaimPreviewTile from './view';
|
import ClaimPreviewTile from './view';
|
||||||
|
@ -31,9 +30,7 @@ const select = (state, props) => {
|
||||||
isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state),
|
isResolvingUri: props.uri && makeSelectIsUriResolving(props.uri)(state),
|
||||||
thumbnail: props.uri && makeSelectThumbnailForUri(props.uri)(state),
|
thumbnail: props.uri && makeSelectThumbnailForUri(props.uri)(state),
|
||||||
title: props.uri && makeSelectTitleForUri(props.uri)(state),
|
title: props.uri && makeSelectTitleForUri(props.uri)(state),
|
||||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
banState: selectBanStateForUri(state, props.uri),
|
||||||
filteredOutpoints: selectFilteredOutpoints(state),
|
|
||||||
blockedChannelUris: selectMutedChannels(state),
|
|
||||||
showMature: selectShowMatureContent(state),
|
showMature: selectShowMatureContent(state),
|
||||||
isMature: makeSelectClaimIsNsfw(props.uri)(state),
|
isMature: makeSelectClaimIsNsfw(props.uri)(state),
|
||||||
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
|
||||||
|
|
|
@ -12,7 +12,7 @@ import SubscribeButton from 'component/subscribeButton';
|
||||||
import useGetThumbnail from 'effects/use-get-thumbnail';
|
import useGetThumbnail from 'effects/use-get-thumbnail';
|
||||||
import { formatLbryUrlForWeb, generateListSearchUrlParams } from 'util/url';
|
import { formatLbryUrlForWeb, generateListSearchUrlParams } from 'util/url';
|
||||||
import { formatClaimPreviewTitle } from 'util/formatAriaLabel';
|
import { formatClaimPreviewTitle } from 'util/formatAriaLabel';
|
||||||
import { parseURI, isURIEqual } from 'util/lbryURI';
|
import { parseURI } from 'util/lbryURI';
|
||||||
import PreviewOverlayProperties from 'component/previewOverlayProperties';
|
import PreviewOverlayProperties from 'component/previewOverlayProperties';
|
||||||
import FileDownloadLink from 'component/fileDownloadLink';
|
import FileDownloadLink from 'component/fileDownloadLink';
|
||||||
import FileWatchLaterLink from 'component/fileWatchLaterLink';
|
import FileWatchLaterLink from 'component/fileWatchLaterLink';
|
||||||
|
@ -33,15 +33,7 @@ type Props = {
|
||||||
thumbnail: string,
|
thumbnail: string,
|
||||||
title: string,
|
title: string,
|
||||||
placeholder: boolean,
|
placeholder: boolean,
|
||||||
blackListedOutpoints: Array<{
|
banState: { blacklisted?: boolean, filtered?: boolean, muted?: boolean, blocked?: boolean },
|
||||||
txid: string,
|
|
||||||
nout: number,
|
|
||||||
}>,
|
|
||||||
filteredOutpoints: Array<{
|
|
||||||
txid: string,
|
|
||||||
nout: number,
|
|
||||||
}>,
|
|
||||||
blockedChannelUris: Array<string>,
|
|
||||||
getFile: (string) => void,
|
getFile: (string) => void,
|
||||||
streamingUrl: string,
|
streamingUrl: string,
|
||||||
isMature: boolean,
|
isMature: boolean,
|
||||||
|
@ -67,11 +59,9 @@ function ClaimPreviewTile(props: Props) {
|
||||||
resolveUri,
|
resolveUri,
|
||||||
claim,
|
claim,
|
||||||
placeholder,
|
placeholder,
|
||||||
blackListedOutpoints,
|
banState,
|
||||||
filteredOutpoints,
|
|
||||||
getFile,
|
getFile,
|
||||||
streamingUrl,
|
streamingUrl,
|
||||||
blockedChannelUris,
|
|
||||||
isMature,
|
isMature,
|
||||||
showMature,
|
showMature,
|
||||||
showHiddenByUser,
|
showHiddenByUser,
|
||||||
|
@ -145,34 +135,9 @@ function ClaimPreviewTile(props: Props) {
|
||||||
// Unfortunately needed until this is resolved
|
// Unfortunately needed until this is resolved
|
||||||
// https://github.com/lbryio/lbry-sdk/issues/2785
|
// https://github.com/lbryio/lbry-sdk/issues/2785
|
||||||
shouldHide = true;
|
shouldHide = true;
|
||||||
}
|
} else {
|
||||||
|
shouldHide =
|
||||||
// This will be replaced once blocking is done at the wallet server level
|
banState.blacklisted || banState.filtered || (!showHiddenByUser && (banState.muted || banState.blocked));
|
||||||
if (claim && !shouldHide && blackListedOutpoints) {
|
|
||||||
shouldHide = blackListedOutpoints.some(
|
|
||||||
(outpoint) =>
|
|
||||||
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
|
|
||||||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// We're checking to see if the stream outpoint
|
|
||||||
// or signing channel outpoint is in the filter list
|
|
||||||
if (claim && !shouldHide && filteredOutpoints) {
|
|
||||||
shouldHide = filteredOutpoints.some(
|
|
||||||
(outpoint) =>
|
|
||||||
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
|
|
||||||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// block stream claims
|
|
||||||
if (claim && !shouldHide && !showHiddenByUser && blockedChannelUris.length && signingChannel) {
|
|
||||||
shouldHide = blockedChannelUris.some((blockedUri) => isURIEqual(blockedUri, signingChannel.permanent_url));
|
|
||||||
}
|
|
||||||
// block channel claims if we can't control for them in claim search
|
|
||||||
// e.g. fetchRecommendedSubscriptions
|
|
||||||
if (claim && isChannel && !shouldHide && !showHiddenByUser && blockedChannelUris.length && signingChannel) {
|
|
||||||
shouldHide = blockedChannelUris.some((blockedUri) => isURIEqual(blockedUri, signingChannel.permanent_url));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldHide || (isLivestream && !showNoSourceClaims)) {
|
if (shouldHide || (isLivestream && !showNoSourceClaims)) {
|
||||||
|
@ -290,34 +255,4 @@ function ClaimPreviewTile(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default React.memo<Props>(withRouter(ClaimPreviewTile), areEqual);
|
export default withRouter(ClaimPreviewTile);
|
||||||
|
|
||||||
const BLOCKLIST_KEYS = ['blackListedOutpoints', 'filteredOutpoints', 'blockedChannelUris'];
|
|
||||||
const HANDLED_KEYS = [...BLOCKLIST_KEYS, 'date'];
|
|
||||||
|
|
||||||
function areEqual(prev: Props, next: Props) {
|
|
||||||
for (let i = 0; i < BLOCKLIST_KEYS.length; ++i) {
|
|
||||||
const key = BLOCKLIST_KEYS[i];
|
|
||||||
const a = prev[key];
|
|
||||||
const b = next[key];
|
|
||||||
|
|
||||||
if (((!a || !b) && a !== b) || (a && b && a.length !== b.length)) {
|
|
||||||
// The arrays are huge, so just compare the length instead of each entry.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Number(prev.date) !== Number(next.date)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const propKeys = Object.keys(next);
|
|
||||||
for (let i = 0; i < propKeys.length; ++i) {
|
|
||||||
const pk = propKeys[i];
|
|
||||||
if (!HANDLED_KEYS.includes(pk) && prev[pk] !== next[pk]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue