From 50c606d916e7497e3266f16b78e31e80b5a3d848 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Wed, 21 Jul 2021 15:55:14 +0800 Subject: [PATCH] claimListDiscover: don't re-render until query is done ## Issue 6542 Livestream listing and blocked list kills CLS score This is basically a repeat of what was done on `claimTileDiscover` (homepage), but now on `claimListDiscover` (category pages). This handles the unnecessary re-render when: - the uri list temporarily being zero while waiting for new claim-search results. - livestream claim-search invalidating the list. ## Changes Store the last uri list and use that when we know that claim-search is still not done. ## Tests 1. Ran Lighthouse on category pages (force a livestream channel on it). CLS score must be green. 2. Block `claim_search` from Dev Tools and move to another category. The page should say "timed out" instead of "no results" (i.e. maintain existing behavior). --- ui/component/claimListDiscover/view.jsx | 47 +++++++++++++++++++++---- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/ui/component/claimListDiscover/view.jsx b/ui/component/claimListDiscover/view.jsx index aae47f32d..40ccc20f5 100644 --- a/ui/component/claimListDiscover/view.jsx +++ b/ui/component/claimListDiscover/view.jsx @@ -141,6 +141,7 @@ function ClaimListDiscover(props: Props) { const { search } = location; const [page, setPage] = React.useState(1); const [forceRefresh, setForceRefresh] = React.useState(); + const [finalUris, setFinalUris] = React.useState([]); const isLargeScreen = useIsLargeScreen(); const [orderParamEntry, setOrderParamEntry] = usePersistedState(`entry-${location.pathname}`, CS.ORDER_BY_TRENDING); const [orderParamUser, setOrderParamUser] = usePersistedState(`orderUser-${location.pathname}`, CS.ORDER_BY_TRENDING); @@ -360,9 +361,10 @@ function ClaimListDiscover(props: Props) { } const hasMatureTags = tagsParam && tagsParam.split(',').some((t) => MATURE_TAGS.includes(t)); - const claimSearchCacheQuery = createNormalizedClaimSearchKey(options); - let claimSearchResult = claimSearchByQuery[claimSearchCacheQuery]; - const claimSearchResultLastPageReached = claimSearchByQueryLastPageReached[claimSearchCacheQuery]; + + const mainSearchKey = createNormalizedClaimSearchKey(options); + let claimSearchResult = claimSearchByQuery[mainSearchKey]; + const claimSearchResultLastPageReached = claimSearchByQueryLastPageReached[mainSearchKey]; // uncomment to fix an item on a page // const fixUri = 'lbry://@corbettreport#0/lbryodysee#5'; @@ -380,6 +382,11 @@ function ClaimListDiscover(props: Props) { // claimSearchResult.splice(2, 0, fixUri); // } + const livestreamSearchKey = liveLivestreamsFirst + ? createNormalizedClaimSearchKey(getLivestreamOnlyOptions(options)) + : undefined; + const livestreamSearchResult = livestreamSearchKey && claimSearchByQuery[livestreamSearchKey]; + const [prevOptions, setPrevOptions] = React.useState(null); if (!isJustScrollingToNewPage(prevOptions, options)) { @@ -482,6 +489,17 @@ function ClaimListDiscover(props: Props) { } } + function urisEqual(prev: Array, next: Array) { + if (!prev || !next) { + // From 'ClaimList', "null" and "undefined" have special meaning, + // so we can't just compare array length here. + // - null = "timed out" + // - undefined = "no result". + return prev === next; + } + return prev.length === next.length && prev.every((value, index) => value === next[index]); + } + React.useEffect(() => { if (shouldPerformSearch) { const searchOptions = JSON.parse(optionsStringForEffect); @@ -493,6 +511,21 @@ function ClaimListDiscover(props: Props) { } }, [doClaimSearch, shouldPerformSearch, optionsStringForEffect, forceRefresh]); + // Resolve 'finalUri' + React.useEffect(() => { + if (uris) { + if (!urisEqual(uris, finalUris)) { + setFinalUris(uris); + } + } else { + // Wait until all queries are done before updating the uris to avoid layout shifts. + const pending = claimSearchResult === undefined || (liveLivestreamsFirst && livestreamSearchResult === undefined); + if (!pending && !urisEqual(claimSearchResult, finalUris)) { + setFinalUris(claimSearchResult); + } + } + }, [uris, claimSearchResult, finalUris, setFinalUris, liveLivestreamsFirst, livestreamSearchResult]); + const headerToUse = header || ( )}