ClaimTilesDiscover: optimize props to reduce renders
Props: either use primitives, stable references or memoized objects to reduce renders. This update will simplify the existing `areEqual`. It is still needed though as some props are hard to memoize from where they are called, or should simply be ignore.
This commit is contained in:
parent
b1f4a2a590
commit
b0bea66b1d
3 changed files with 70 additions and 69 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import { selectClaimSearchByQuery, selectFetchingClaimSearchByQuery, selectClaimsByUri } from 'redux/selectors/claims';
|
import { selectClaimSearchByQuery, selectFetchingClaimSearchByQuery, selectClaimsByUri } from 'redux/selectors/claims';
|
||||||
|
@ -9,6 +10,7 @@ import { selectClientSetting, selectShowMatureContent } from 'redux/selectors/se
|
||||||
import { selectMutedAndBlockedChannelIds } from 'redux/selectors/blocked';
|
import { selectMutedAndBlockedChannelIds } from 'redux/selectors/blocked';
|
||||||
import { ENABLE_NO_SOURCE_CLAIMS, SIMPLE_SITE } from 'config';
|
import { ENABLE_NO_SOURCE_CLAIMS, SIMPLE_SITE } from 'config';
|
||||||
import * as CS from 'constants/claim_search';
|
import * as CS from 'constants/claim_search';
|
||||||
|
import { createNormalizedClaimSearchKey } from 'util/claim';
|
||||||
|
|
||||||
import ClaimListDiscover from './view';
|
import ClaimListDiscover from './view';
|
||||||
|
|
||||||
|
@ -17,13 +19,18 @@ const select = (state, props) => {
|
||||||
const hideReposts = selectClientSetting(state, SETTINGS.HIDE_REPOSTS);
|
const hideReposts = selectClientSetting(state, SETTINGS.HIDE_REPOSTS);
|
||||||
const mutedAndBlockedChannelIds = selectMutedAndBlockedChannelIds(state);
|
const mutedAndBlockedChannelIds = selectMutedAndBlockedChannelIds(state);
|
||||||
|
|
||||||
|
// TODO: memoize these 2 function calls. Lots of params, though; might not be feasible.
|
||||||
|
const options = resolveSearchOptions({ showNsfw, hideReposts, mutedAndBlockedChannelIds, pageSize: 8, ...props });
|
||||||
|
const searchKey = createNormalizedClaimSearchKey(options);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claimSearchByQuery: selectClaimSearchByQuery(state),
|
claimSearchResults: selectClaimSearchByQuery(state)[searchKey],
|
||||||
claimsByUri: selectClaimsByUri(state),
|
claimsByUri: selectClaimsByUri(state),
|
||||||
fetchingClaimSearchByQuery: selectFetchingClaimSearchByQuery(state),
|
fetchingClaimSearch: selectFetchingClaimSearchByQuery(state)[searchKey],
|
||||||
showNsfw,
|
showNsfw,
|
||||||
hideReposts,
|
hideReposts,
|
||||||
options: resolveSearchOptions({ showNsfw, hideReposts, mutedAndBlockedChannelIds, pageSize: 8, ...props }),
|
// Don't use the query from 'createNormalizedClaimSearchKey(options)' since that doesn't include page & release_time
|
||||||
|
optionsStringified: JSON.stringify(options),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,6 +44,27 @@ export default withRouter(connect(select, perform)(ClaimListDiscover));
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
|
|
||||||
|
type SearchOptions = {
|
||||||
|
page_size: number,
|
||||||
|
page: number,
|
||||||
|
no_totals: boolean,
|
||||||
|
any_tags: Array<string>,
|
||||||
|
channel_ids: Array<string>,
|
||||||
|
claim_ids?: Array<string>,
|
||||||
|
not_channel_ids: Array<string>,
|
||||||
|
not_tags: Array<string>,
|
||||||
|
order_by: Array<string>,
|
||||||
|
languages?: Array<string>,
|
||||||
|
release_time?: string,
|
||||||
|
claim_type?: string | Array<string>,
|
||||||
|
timestamp?: string,
|
||||||
|
fee_amount?: string,
|
||||||
|
limit_claims_per_channel?: number,
|
||||||
|
stream_types?: Array<string>,
|
||||||
|
has_source?: boolean,
|
||||||
|
has_no_source?: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
function resolveSearchOptions(props) {
|
function resolveSearchOptions(props) {
|
||||||
const {
|
const {
|
||||||
showNsfw,
|
showNsfw,
|
||||||
|
@ -70,7 +98,8 @@ function resolveSearchOptions(props) {
|
||||||
streamTypesParam = [CS.FILE_VIDEO, CS.FILE_AUDIO];
|
streamTypesParam = [CS.FILE_VIDEO, CS.FILE_AUDIO];
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
const options: SearchOptions = {
|
||||||
|
page: 1,
|
||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
claim_type: claimType || ['stream', 'repost', 'channel'],
|
claim_type: claimType || ['stream', 'repost', 'channel'],
|
||||||
// no_totals makes it so the sdk doesn't have to calculate total number pages for pagination
|
// no_totals makes it so the sdk doesn't have to calculate total number pages for pagination
|
||||||
|
|
|
@ -1,32 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createNormalizedClaimSearchKey } from 'util/claim';
|
|
||||||
import ClaimPreviewTile from 'component/claimPreviewTile';
|
import ClaimPreviewTile from 'component/claimPreviewTile';
|
||||||
import useFetchViewCount from 'effects/use-fetch-view-count';
|
import useFetchViewCount from 'effects/use-fetch-view-count';
|
||||||
import usePrevious from 'effects/use-previous';
|
import usePrevious from 'effects/use-previous';
|
||||||
|
|
||||||
type SearchOptions = {
|
|
||||||
page_size: number,
|
|
||||||
page: number,
|
|
||||||
no_totals: boolean,
|
|
||||||
any_tags: Array<string>,
|
|
||||||
channel_ids: Array<string>,
|
|
||||||
claim_ids?: Array<string>,
|
|
||||||
not_channel_ids: Array<string>,
|
|
||||||
not_tags: Array<string>,
|
|
||||||
order_by: Array<string>,
|
|
||||||
languages?: Array<string>,
|
|
||||||
release_time?: string,
|
|
||||||
claim_type?: string | Array<string>,
|
|
||||||
timestamp?: string,
|
|
||||||
fee_amount?: string,
|
|
||||||
limit_claims_per_channel?: number,
|
|
||||||
stream_types?: Array<string>,
|
|
||||||
has_source?: boolean,
|
|
||||||
has_no_source?: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
function urisEqual(prev: ?Array<string>, next: ?Array<string>) {
|
function urisEqual(prev: ?Array<string>, next: ?Array<string>) {
|
||||||
if (!prev || !next) {
|
if (!prev || !next) {
|
||||||
// ClaimList: "null" and "undefined" have special meaning,
|
// ClaimList: "null" and "undefined" have special meaning,
|
||||||
|
@ -68,12 +46,12 @@ type Props = {
|
||||||
hasNoSource?: boolean,
|
hasNoSource?: boolean,
|
||||||
// --- select ---
|
// --- select ---
|
||||||
location: { search: string },
|
location: { search: string },
|
||||||
claimSearchByQuery: { [string]: Array<string> },
|
claimSearchResults: Array<string>,
|
||||||
claimsByUri: { [string]: any },
|
claimsByUri: { [string]: any },
|
||||||
fetchingClaimSearchByQuery: { [string]: boolean },
|
fetchingClaimSearch: boolean,
|
||||||
showNsfw: boolean,
|
showNsfw: boolean,
|
||||||
hideReposts: boolean,
|
hideReposts: boolean,
|
||||||
options: SearchOptions,
|
optionsStringified: string,
|
||||||
// --- perform ---
|
// --- perform ---
|
||||||
doClaimSearch: ({}) => void,
|
doClaimSearch: ({}) => void,
|
||||||
doFetchViewCount: (claimIdCsv: string) => void,
|
doFetchViewCount: (claimIdCsv: string) => void,
|
||||||
|
@ -82,10 +60,10 @@ type Props = {
|
||||||
function ClaimTilesDiscover(props: Props) {
|
function ClaimTilesDiscover(props: Props) {
|
||||||
const {
|
const {
|
||||||
doClaimSearch,
|
doClaimSearch,
|
||||||
claimSearchByQuery,
|
claimSearchResults,
|
||||||
claimsByUri,
|
claimsByUri,
|
||||||
fetchViewCount,
|
fetchViewCount,
|
||||||
fetchingClaimSearchByQuery,
|
fetchingClaimSearch,
|
||||||
hasNoSource,
|
hasNoSource,
|
||||||
renderProperties,
|
renderProperties,
|
||||||
pinUrls,
|
pinUrls,
|
||||||
|
@ -93,16 +71,12 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
showNoSourceClaims,
|
showNoSourceClaims,
|
||||||
doFetchViewCount,
|
doFetchViewCount,
|
||||||
pageSize = 8,
|
pageSize = 8,
|
||||||
options,
|
optionsStringified,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const searchKey = createNormalizedClaimSearchKey(options);
|
const claimSearchUris = claimSearchResults || [];
|
||||||
const fetchingClaimSearch = fetchingClaimSearchByQuery[searchKey];
|
const isUnfetchedClaimSearch = claimSearchResults === undefined;
|
||||||
const claimSearchUris = claimSearchByQuery[searchKey] || [];
|
|
||||||
const isUnfetchedClaimSearch = claimSearchByQuery[searchKey] === undefined;
|
|
||||||
|
|
||||||
// Don't use the query from createNormalizedClaimSearchKey for the effect since that doesn't include page & release_time
|
|
||||||
const optionsStringForEffect = JSON.stringify(options);
|
|
||||||
const shouldPerformSearch = !fetchingClaimSearch && claimSearchUris.length === 0;
|
const shouldPerformSearch = !fetchingClaimSearch && claimSearchUris.length === 0;
|
||||||
|
|
||||||
const uris = (prefixUris || []).concat(claimSearchUris);
|
const uris = (prefixUris || []).concat(claimSearchUris);
|
||||||
|
@ -126,18 +100,21 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
|
|
||||||
const prevUris = usePrevious(uris);
|
const prevUris = usePrevious(uris);
|
||||||
|
|
||||||
|
// Show previous results while we fetch to avoid blinkies and poor CLS.
|
||||||
|
const finalUris = isUnfetchedClaimSearch && prevUris ? prevUris : uris;
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
useFetchViewCount(fetchViewCount, uris, claimsByUri, doFetchViewCount);
|
useFetchViewCount(fetchViewCount, uris, claimsByUri, doFetchViewCount);
|
||||||
|
|
||||||
// Run `doClaimSearch`
|
// Run `doClaimSearch`
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (shouldPerformSearch) {
|
if (shouldPerformSearch) {
|
||||||
const searchOptions = JSON.parse(optionsStringForEffect);
|
const searchOptions = JSON.parse(optionsStringified);
|
||||||
doClaimSearch(searchOptions);
|
doClaimSearch(searchOptions);
|
||||||
}
|
}
|
||||||
}, [doClaimSearch, shouldPerformSearch, optionsStringForEffect]);
|
}, [doClaimSearch, shouldPerformSearch, optionsStringified]);
|
||||||
|
|
||||||
// Show previous results while we fetch to avoid blinkies and poor CLS.
|
|
||||||
const finalUris = isUnfetchedClaimSearch && prevUris ? prevUris : uris;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="claim-grid">
|
<ul className="claim-grid">
|
||||||
|
@ -167,33 +144,33 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
|
|
||||||
export default React.memo<Props>(ClaimTilesDiscover, areEqual);
|
export default React.memo<Props>(ClaimTilesDiscover, areEqual);
|
||||||
|
|
||||||
function debug_trace(val) {
|
// ****************************************************************************
|
||||||
if (process.env.DEBUG_TRACE) console.log(`Render due to: ${val}`);
|
// ****************************************************************************
|
||||||
|
|
||||||
|
function trace(key, value) {
|
||||||
|
// @if process.env.DEBUG_TILE_RENDER
|
||||||
|
// $FlowFixMe "cannot coerce certain types".
|
||||||
|
console.log(`[claimTilesDiscover] ${key}: ${value}`); // eslint-disable-line no-console
|
||||||
|
// @endif
|
||||||
}
|
}
|
||||||
|
|
||||||
function areEqual(prev: Props, next: Props) {
|
function areEqual(prev: Props, next: Props) {
|
||||||
const prevOptions: SearchOptions = prev.options;
|
|
||||||
const nextOptions: SearchOptions = next.options;
|
|
||||||
|
|
||||||
const prevSearchKey = createNormalizedClaimSearchKey(prevOptions);
|
|
||||||
const nextSearchKey = createNormalizedClaimSearchKey(nextOptions);
|
|
||||||
|
|
||||||
if (prevSearchKey !== nextSearchKey) {
|
|
||||||
debug_trace('search key');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Deep-compare ---
|
// --- Deep-compare ---
|
||||||
if (!urisEqual(prev.claimSearchByQuery[prevSearchKey], next.claimSearchByQuery[nextSearchKey])) {
|
// These are props that are hard to memoize from where it is passed.
|
||||||
debug_trace('claimSearchByQuery');
|
|
||||||
return false;
|
if (prev.claimType !== next.claimType) {
|
||||||
|
// Array<string>: confirm the contents are actually different.
|
||||||
|
if (prev.claimType && next.claimType && JSON.stringify(prev.claimType) !== JSON.stringify(next.claimType)) {
|
||||||
|
trace('claimType', next.claimType);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ARRAY_KEYS = ['prefixUris', 'channelIds'];
|
const ARRAY_KEYS = ['prefixUris', 'channelIds'];
|
||||||
for (let i = 0; i < ARRAY_KEYS.length; ++i) {
|
for (let i = 0; i < ARRAY_KEYS.length; ++i) {
|
||||||
const key = ARRAY_KEYS[i];
|
const key = ARRAY_KEYS[i];
|
||||||
if (!urisEqual(prev[key], next[key])) {
|
if (!urisEqual(prev[key], next[key])) {
|
||||||
debug_trace(`${key}`);
|
trace(key, next[key]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,13 +180,11 @@ function areEqual(prev: Props, next: Props) {
|
||||||
// to update this function. Better to render more than miss an important one.
|
// to update this function. Better to render more than miss an important one.
|
||||||
const KEYS_TO_IGNORE = [
|
const KEYS_TO_IGNORE = [
|
||||||
...ARRAY_KEYS,
|
...ARRAY_KEYS,
|
||||||
'claimSearchByQuery',
|
'claimType', // Handled above.
|
||||||
'fetchingClaimSearchByQuery', // We are showing previous results while fetching.
|
'claimsByUri', // Used for view-count. Just ignore it for now.
|
||||||
'options', // Covered by search-key comparison.
|
|
||||||
'location',
|
'location',
|
||||||
'history',
|
'history',
|
||||||
'match',
|
'match',
|
||||||
'claimsByUri',
|
|
||||||
'doClaimSearch',
|
'doClaimSearch',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -217,7 +192,7 @@ function areEqual(prev: Props, next: Props) {
|
||||||
for (let i = 0; i < propKeys.length; ++i) {
|
for (let i = 0; i < propKeys.length; ++i) {
|
||||||
const pk = propKeys[i];
|
const pk = propKeys[i];
|
||||||
if (!KEYS_TO_IGNORE.includes(pk) && prev[pk] !== next[pk]) {
|
if (!KEYS_TO_IGNORE.includes(pk) && prev[pk] !== next[pk]) {
|
||||||
debug_trace(`${pk}`);
|
trace(pk, next[pk]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -616,10 +616,7 @@ export const makeSelectTagsForUri = (uri: string) =>
|
||||||
return (metadata && metadata.tags) || [];
|
return (metadata && metadata.tags) || [];
|
||||||
});
|
});
|
||||||
|
|
||||||
export const selectFetchingClaimSearchByQuery = createSelector(
|
export const selectFetchingClaimSearchByQuery = (state: State) => selectState(state).fetchingClaimSearchByQuery || {};
|
||||||
selectState,
|
|
||||||
(state) => state.fetchingClaimSearchByQuery || {}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectFetchingClaimSearch = createSelector(
|
export const selectFetchingClaimSearch = createSelector(
|
||||||
selectFetchingClaimSearchByQuery,
|
selectFetchingClaimSearchByQuery,
|
||||||
|
|
Loading…
Reference in a new issue