// @flow import type { Node } from 'react'; import React from 'react'; import ClaimPreviewTile from 'component/claimPreviewTile'; import useFetchViewCount from 'effects/use-fetch-view-count'; // type SearchOptions = { // page_size: number, // page: number, // no_totals: boolean, // any_tags: Array, // channel_ids: Array, // claim_ids?: Array, // not_channel_ids: Array, // not_tags: Array, // order_by: Array, // languages?: Array, // release_time?: string, // claim_type?: string | Array, // timestamp?: string, // fee_amount?: string, // limit_claims_per_channel?: number, // stream_types?: Array, // has_source?: boolean, // has_no_source?: boolean, // }; function urisEqual(prev: ?Array, next: ?Array) { if (!prev || !next) { // 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; } // $FlowFixMe - already checked for null above. return prev.length === next.length && prev.every((value, index) => value === next[index]); } // **************************************************************************** // ClaimTilesDiscover // **************************************************************************** type Props = { prefixUris?: Array, pinUrls?: Array, uris: Array, showNoSourceClaims?: boolean, renderProperties?: (Claim) => ?Node, fetchViewCount?: boolean, // claim search options are below tags: Array, claimIds?: Array, channelIds?: Array, pageSize: number, orderBy?: Array, releaseTime?: string, languages?: Array, claimType?: string | Array, streamTypes?: Array, timestamp?: string, feeAmount?: string, limitClaimsPerChannel?: number, hasSource?: boolean, hasNoSource?: boolean, // --- select --- location: { search: string }, claimSearchResults: Array, claimsByUri: { [string]: any }, fetchingClaimSearch: boolean, showNsfw: boolean, hideReposts: boolean, optionsStringified: string, // --- perform --- doClaimSearch: ({}) => void, doFetchViewCount: (claimIdCsv: string) => void, }; function ClaimTilesDiscover(props: Props) { const { doClaimSearch, claimSearchResults, claimsByUri, fetchViewCount, fetchingClaimSearch, hasNoSource, renderProperties, // pinUrls, prefixUris, showNoSourceClaims, doFetchViewCount, pageSize = 8, optionsStringified, } = props; const prevUris = React.useRef(); 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 shouldPerformSearch = !fetchingClaimSearch && claimSearchUris.length === 0; const uris = (prefixUris || []).concat(claimSearchUris); // Not sure what to do with pinUrls // if (pinUrls && uris && uris.length > 2 && window.location.pathname === '/') { // pinUrls.forEach((pin) => { // if (uris.indexOf(pin) !== -1) { // uris.splice(uris.indexOf(pin), 1); // } else { // uris.pop(); // } // }); // uris.splice(2, 0, ...pinUrls); // } if (uris.length > 0 && uris.length < pageSize && shouldPerformSearch) { // prefixUri and pinUrls might already be present while waiting for the // remaining claim_search results. Fill the space to prevent layout shifts. uris.push(...Array(pageSize - uris.length).fill('')); } useFetchViewCount(fetchViewCount, uris, claimsByUri, doFetchViewCount); const finalUris = isUnfetchedClaimSearch && prevUris.current ? prevUris.current : uris; prevUris.current = finalUris; // Run `doClaimSearch` React.useEffect(() => { if (shouldPerformSearch) { const searchOptions = JSON.parse(optionsStringified); doClaimSearch(searchOptions); } }, [doClaimSearch, shouldPerformSearch, optionsStringified]); // Show previous results while we fetch to avoid blinkies and poor CLS. return (
    {finalUris && finalUris.length ? finalUris.map((uri, i) => { if (uri) { return ( ); } else { return ; } }) : new Array(pageSize) .fill(1) .map((x, i) => ( ))}
); } export default React.memo(ClaimTilesDiscover, areEqual); // **************************************************************************** // **************************************************************************** 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) { // --- Deep-compare --- // These are props that are hard to memoize from where it is passed. if (prev.claimType !== next.claimType) { // Array: 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])) { trace(key, next[key]); return false; } } // --- Default the rest(*) to shallow-compare --- // (*) including new props introduced in the future, in case developer forgets // to update this function. Better to render more than miss an important one. const KEYS_TO_IGNORE = [ ...ARRAY_KEYS, 'claimType', // Handled above. 'claimsByUri', // Used for view-count. Just ignore it for now. 'location', 'history', 'match', 'doClaimSearch', ]; const propKeys = Object.keys(next); for (let i = 0; i < propKeys.length; ++i) { const pk = propKeys[i]; if (!KEYS_TO_IGNORE.includes(pk) && prev[pk] !== next[pk]) { trace(pk, next[pk]); return false; } } return true; }