re-reselect proof of concept + fix Date selector as first example
## Issue `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. ## Changes - 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)
This commit is contained in:
parent
9bbd72d179
commit
0c2c21b67e
6 changed files with 63 additions and 17 deletions
|
@ -50,7 +50,6 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ungap/from-entries": "^0.2.1",
|
"@ungap/from-entries": "^0.2.1",
|
||||||
"proxy-polyfill": "0.1.6",
|
|
||||||
"auto-launch": "^5.0.5",
|
"auto-launch": "^5.0.5",
|
||||||
"electron-dl": "^1.11.0",
|
"electron-dl": "^1.11.0",
|
||||||
"electron-log": "^2.2.12",
|
"electron-log": "^2.2.12",
|
||||||
|
@ -61,6 +60,8 @@
|
||||||
"if-env": "^1.0.4",
|
"if-env": "^1.0.4",
|
||||||
"match-sorter": "^6.3.0",
|
"match-sorter": "^6.3.0",
|
||||||
"parse-duration": "^1.0.0",
|
"parse-duration": "^1.0.0",
|
||||||
|
"proxy-polyfill": "0.1.6",
|
||||||
|
"re-reselect": "^4.0.0",
|
||||||
"react-datetime-picker": "^3.2.1",
|
"react-datetime-picker": "^3.2.1",
|
||||||
"react-plastic": "^1.1.1",
|
"react-plastic": "^1.1.1",
|
||||||
"react-top-loading-bar": "^2.0.1",
|
"react-top-loading-bar": "^2.0.1",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
makeSelectClaimForUri,
|
selectClaimForUri,
|
||||||
makeSelectIsUriResolving,
|
makeSelectIsUriResolving,
|
||||||
makeSelectClaimIsMine,
|
makeSelectClaimIsMine,
|
||||||
makeSelectClaimIsPending,
|
makeSelectClaimIsPending,
|
||||||
|
@ -9,7 +9,7 @@ import {
|
||||||
makeSelectClaimWasPurchased,
|
makeSelectClaimWasPurchased,
|
||||||
makeSelectClaimIsStreamPlaceholder,
|
makeSelectClaimIsStreamPlaceholder,
|
||||||
makeSelectTitleForUri,
|
makeSelectTitleForUri,
|
||||||
makeSelectDateForUri,
|
selectDateForUri,
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
|
import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
|
||||||
import {
|
import {
|
||||||
|
@ -32,14 +32,14 @@ import ClaimPreview from './view';
|
||||||
import formatMediaDuration from 'util/formatMediaDuration';
|
import formatMediaDuration from 'util/formatMediaDuration';
|
||||||
|
|
||||||
const select = (state, props) => {
|
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 media = claim && claim.value && (claim.value.video || claim.value.audio);
|
||||||
const mediaDuration = media && media.duration && formatMediaDuration(media.duration, { screenReader: true });
|
const mediaDuration = media && media.duration && formatMediaDuration(media.duration, { screenReader: true });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claim,
|
claim,
|
||||||
mediaDuration,
|
mediaDuration,
|
||||||
date: props.uri && makeSelectDateForUri(props.uri)(state),
|
date: props.uri && selectDateForUri(state, props.uri),
|
||||||
title: props.uri && makeSelectTitleForUri(props.uri)(state),
|
title: props.uri && makeSelectTitleForUri(props.uri)(state),
|
||||||
pending: props.uri && makeSelectClaimIsPending(props.uri)(state),
|
pending: props.uri && makeSelectClaimIsPending(props.uri)(state),
|
||||||
reflectingProgress: props.uri && makeSelectReflectingClaimForUri(props.uri)(state),
|
reflectingProgress: props.uri && makeSelectReflectingClaimForUri(props.uri)(state),
|
||||||
|
@ -47,7 +47,6 @@ const select = (state, props) => {
|
||||||
claimIsMine: props.uri && makeSelectClaimIsMine(props.uri)(state),
|
claimIsMine: props.uri && makeSelectClaimIsMine(props.uri)(state),
|
||||||
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),
|
||||||
repostClaim: props.uri && makeSelectClaimForUri(props.uri)(state),
|
|
||||||
nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state),
|
nsfw: props.uri && makeSelectClaimIsNsfw(props.uri)(state),
|
||||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||||
filteredOutpoints: selectFilteredOutpoints(state),
|
filteredOutpoints: selectFilteredOutpoints(state),
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
makeSelectChannelForClaimUri,
|
makeSelectChannelForClaimUri,
|
||||||
makeSelectClaimIsNsfw,
|
makeSelectClaimIsNsfw,
|
||||||
makeSelectClaimIsStreamPlaceholder,
|
makeSelectClaimIsStreamPlaceholder,
|
||||||
makeSelectDateForUri,
|
selectDateForUri,
|
||||||
} 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';
|
||||||
|
@ -26,7 +26,7 @@ const select = (state, props) => {
|
||||||
return {
|
return {
|
||||||
claim,
|
claim,
|
||||||
mediaDuration,
|
mediaDuration,
|
||||||
date: props.uri && makeSelectDateForUri(props.uri)(state),
|
date: props.uri && selectDateForUri(state, props.uri),
|
||||||
channel: props.uri && makeSelectChannelForClaimUri(props.uri)(state),
|
channel: props.uri && makeSelectChannelForClaimUri(props.uri)(state),
|
||||||
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),
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectDateForUri } from 'redux/selectors/claims';
|
import { selectDateForUri } from 'redux/selectors/claims';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import DateTime from './view';
|
import DateTime from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
date: props.date || makeSelectDateForUri(props.uri)(state),
|
date: props.date || selectDateForUri(state, props.uri),
|
||||||
clock24h: makeSelectClientSetting(SETTINGS.CLOCK_24H)(state),
|
clock24h: makeSelectClientSetting(SETTINGS.CLOCK_24H)(state),
|
||||||
});
|
});
|
||||||
export default connect(select)(DateTime);
|
export default connect(select)(DateTime);
|
||||||
|
|
|
@ -2,20 +2,22 @@
|
||||||
import { normalizeURI, parseURI, isURIValid } from 'util/lbryURI';
|
import { normalizeURI, parseURI, isURIValid } from 'util/lbryURI';
|
||||||
import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
|
import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import { createCachedSelector } from 're-reselect';
|
||||||
import { isClaimNsfw, filterClaims } from 'util/claim';
|
import { isClaimNsfw, filterClaims } from 'util/claim';
|
||||||
import * as CLAIM from 'constants/claim';
|
import * as CLAIM from 'constants/claim';
|
||||||
|
|
||||||
|
type State = { claims: any };
|
||||||
|
|
||||||
const selectState = (state) => state.claims || {};
|
const selectState = (state) => state.claims || {};
|
||||||
|
|
||||||
export const selectById = createSelector(selectState, (state) => state.byId || {});
|
export const selectById = (state: State) => selectState(state).byId || {};
|
||||||
|
export const selectPendingClaimsById = (state: State) => selectState(state).pendingById || {};
|
||||||
export const selectPendingClaimsById = createSelector(selectState, (state) => state.pendingById || {});
|
|
||||||
|
|
||||||
export const selectClaimsById = createSelector(selectById, selectPendingClaimsById, (byId, pendingById) => {
|
export const selectClaimsById = createSelector(selectById, selectPendingClaimsById, (byId, pendingById) => {
|
||||||
return Object.assign(byId, pendingById); // do I need merged to keep metadata?
|
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);
|
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 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) =>
|
export const makeSelectClaimForUri = (uri: string, returnRepost: boolean = true) =>
|
||||||
createSelector(selectClaimIdsByUri, selectClaimsById, (byUri, byId) => {
|
createSelector(selectClaimIdsByUri, selectClaimsById, (byUri, byId) => {
|
||||||
const validUri = isURIValid(uri);
|
const validUri = isURIValid(uri);
|
||||||
|
@ -245,8 +284,9 @@ export const makeSelectMetadataItemForUri = (uri: string, key: string) =>
|
||||||
export const makeSelectTitleForUri = (uri: string) =>
|
export const makeSelectTitleForUri = (uri: string) =>
|
||||||
createSelector(makeSelectMetadataForUri(uri), (metadata) => metadata && metadata.title);
|
createSelector(makeSelectMetadataForUri(uri), (metadata) => metadata && metadata.title);
|
||||||
|
|
||||||
export const makeSelectDateForUri = (uri: string) =>
|
export const selectDateForUri = createCachedSelector(
|
||||||
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
selectClaimForUri, // input: (state, uri, ?returnRepost)
|
||||||
|
(claim) => {
|
||||||
const timestamp =
|
const timestamp =
|
||||||
claim &&
|
claim &&
|
||||||
claim.value &&
|
claim.value &&
|
||||||
|
@ -260,7 +300,8 @@ export const makeSelectDateForUri = (uri: string) =>
|
||||||
}
|
}
|
||||||
const dateObj = new Date(timestamp);
|
const dateObj = new Date(timestamp);
|
||||||
return dateObj;
|
return dateObj;
|
||||||
});
|
}
|
||||||
|
)((state, uri) => uri);
|
||||||
|
|
||||||
export const makeSelectAmountForUri = (uri: string) =>
|
export const makeSelectAmountForUri = (uri: string) =>
|
||||||
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
||||||
|
|
|
@ -13317,6 +13317,11 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.8:
|
||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
strip-json-comments "~2.0.1"
|
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:
|
react-async-script@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-async-script/-/react-async-script-1.1.1.tgz#f481c6c5f094bf4b94a9d52da0d0dda2e1a74bdf"
|
resolved "https://registry.yarnpkg.com/react-async-script/-/react-async-script-1.1.1.tgz#f481c6c5f094bf4b94a9d52da0d0dda2e1a74bdf"
|
||||||
|
|
Loading…
Reference in a new issue