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 { withRouter } from 'react-router';
|
||||
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 { ENABLE_NO_SOURCE_CLAIMS, SIMPLE_SITE } from 'config';
|
||||
import * as CS from 'constants/claim_search';
|
||||
import { createNormalizedClaimSearchKey } from 'util/claim';
|
||||
|
||||
import ClaimListDiscover from './view';
|
||||
|
||||
|
@ -17,13 +19,18 @@ const select = (state, props) => {
|
|||
const hideReposts = selectClientSetting(state, SETTINGS.HIDE_REPOSTS);
|
||||
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 {
|
||||
claimSearchByQuery: selectClaimSearchByQuery(state),
|
||||
claimSearchResults: selectClaimSearchByQuery(state)[searchKey],
|
||||
claimsByUri: selectClaimsByUri(state),
|
||||
fetchingClaimSearchByQuery: selectFetchingClaimSearchByQuery(state),
|
||||
fetchingClaimSearch: selectFetchingClaimSearchByQuery(state)[searchKey],
|
||||
showNsfw,
|
||||
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) {
|
||||
const {
|
||||
showNsfw,
|
||||
|
@ -70,7 +98,8 @@ function resolveSearchOptions(props) {
|
|||
streamTypesParam = [CS.FILE_VIDEO, CS.FILE_AUDIO];
|
||||
}
|
||||
|
||||
const options = {
|
||||
const options: SearchOptions = {
|
||||
page: 1,
|
||||
page_size: pageSize,
|
||||
claim_type: claimType || ['stream', 'repost', 'channel'],
|
||||
// no_totals makes it so the sdk doesn't have to calculate total number pages for pagination
|
||||
|
|
|
@ -1,32 +1,10 @@
|
|||
// @flow
|
||||
import type { Node } from 'react';
|
||||
import React from 'react';
|
||||
import { createNormalizedClaimSearchKey } from 'util/claim';
|
||||
import ClaimPreviewTile from 'component/claimPreviewTile';
|
||||
import useFetchViewCount from 'effects/use-fetch-view-count';
|
||||
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>) {
|
||||
if (!prev || !next) {
|
||||
// ClaimList: "null" and "undefined" have special meaning,
|
||||
|
@ -68,12 +46,12 @@ type Props = {
|
|||
hasNoSource?: boolean,
|
||||
// --- select ---
|
||||
location: { search: string },
|
||||
claimSearchByQuery: { [string]: Array<string> },
|
||||
claimSearchResults: Array<string>,
|
||||
claimsByUri: { [string]: any },
|
||||
fetchingClaimSearchByQuery: { [string]: boolean },
|
||||
fetchingClaimSearch: boolean,
|
||||
showNsfw: boolean,
|
||||
hideReposts: boolean,
|
||||
options: SearchOptions,
|
||||
optionsStringified: string,
|
||||
// --- perform ---
|
||||
doClaimSearch: ({}) => void,
|
||||
doFetchViewCount: (claimIdCsv: string) => void,
|
||||
|
@ -82,10 +60,10 @@ type Props = {
|
|||
function ClaimTilesDiscover(props: Props) {
|
||||
const {
|
||||
doClaimSearch,
|
||||
claimSearchByQuery,
|
||||
claimSearchResults,
|
||||
claimsByUri,
|
||||
fetchViewCount,
|
||||
fetchingClaimSearchByQuery,
|
||||
fetchingClaimSearch,
|
||||
hasNoSource,
|
||||
renderProperties,
|
||||
pinUrls,
|
||||
|
@ -93,16 +71,12 @@ function ClaimTilesDiscover(props: Props) {
|
|||
showNoSourceClaims,
|
||||
doFetchViewCount,
|
||||
pageSize = 8,
|
||||
options,
|
||||
optionsStringified,
|
||||
} = props;
|
||||
|
||||
const searchKey = createNormalizedClaimSearchKey(options);
|
||||
const fetchingClaimSearch = fetchingClaimSearchByQuery[searchKey];
|
||||
const claimSearchUris = claimSearchByQuery[searchKey] || [];
|
||||
const isUnfetchedClaimSearch = claimSearchByQuery[searchKey] === undefined;
|
||||
const claimSearchUris = claimSearchResults || [];
|
||||
const isUnfetchedClaimSearch = claimSearchResults === 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 uris = (prefixUris || []).concat(claimSearchUris);
|
||||
|
@ -126,18 +100,21 @@ function ClaimTilesDiscover(props: Props) {
|
|||
|
||||
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);
|
||||
|
||||
// Run `doClaimSearch`
|
||||
React.useEffect(() => {
|
||||
if (shouldPerformSearch) {
|
||||
const searchOptions = JSON.parse(optionsStringForEffect);
|
||||
const searchOptions = JSON.parse(optionsStringified);
|
||||
doClaimSearch(searchOptions);
|
||||
}
|
||||
}, [doClaimSearch, shouldPerformSearch, optionsStringForEffect]);
|
||||
|
||||
// Show previous results while we fetch to avoid blinkies and poor CLS.
|
||||
const finalUris = isUnfetchedClaimSearch && prevUris ? prevUris : uris;
|
||||
}, [doClaimSearch, shouldPerformSearch, optionsStringified]);
|
||||
|
||||
return (
|
||||
<ul className="claim-grid">
|
||||
|
@ -167,33 +144,33 @@ function ClaimTilesDiscover(props: Props) {
|
|||
|
||||
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) {
|
||||
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 ---
|
||||
if (!urisEqual(prev.claimSearchByQuery[prevSearchKey], next.claimSearchByQuery[nextSearchKey])) {
|
||||
debug_trace('claimSearchByQuery');
|
||||
return false;
|
||||
// These are props that are hard to memoize from where it is passed.
|
||||
|
||||
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'];
|
||||
for (let i = 0; i < ARRAY_KEYS.length; ++i) {
|
||||
const key = ARRAY_KEYS[i];
|
||||
if (!urisEqual(prev[key], next[key])) {
|
||||
debug_trace(`${key}`);
|
||||
trace(key, next[key]);
|
||||
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.
|
||||
const KEYS_TO_IGNORE = [
|
||||
...ARRAY_KEYS,
|
||||
'claimSearchByQuery',
|
||||
'fetchingClaimSearchByQuery', // We are showing previous results while fetching.
|
||||
'options', // Covered by search-key comparison.
|
||||
'claimType', // Handled above.
|
||||
'claimsByUri', // Used for view-count. Just ignore it for now.
|
||||
'location',
|
||||
'history',
|
||||
'match',
|
||||
'claimsByUri',
|
||||
'doClaimSearch',
|
||||
];
|
||||
|
||||
|
@ -217,7 +192,7 @@ function areEqual(prev: Props, next: Props) {
|
|||
for (let i = 0; i < propKeys.length; ++i) {
|
||||
const pk = propKeys[i];
|
||||
if (!KEYS_TO_IGNORE.includes(pk) && prev[pk] !== next[pk]) {
|
||||
debug_trace(`${pk}`);
|
||||
trace(pk, next[pk]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -616,10 +616,7 @@ export const makeSelectTagsForUri = (uri: string) =>
|
|||
return (metadata && metadata.tags) || [];
|
||||
});
|
||||
|
||||
export const selectFetchingClaimSearchByQuery = createSelector(
|
||||
selectState,
|
||||
(state) => state.fetchingClaimSearchByQuery || {}
|
||||
);
|
||||
export const selectFetchingClaimSearchByQuery = (state: State) => selectState(state).fetchingClaimSearchByQuery || {};
|
||||
|
||||
export const selectFetchingClaimSearch = createSelector(
|
||||
selectFetchingClaimSearchByQuery,
|
||||
|
|
Loading…
Reference in a new issue