Fix efficiency problems with visibility checking code (#730)
* Fix problems with visibility checking code * Tear out old contract tracking stuff per James * Use `useEvent` in VisibilityObserver per James suggestion
This commit is contained in:
parent
5715b0e44a
commit
847d3d0f27
|
@ -345,7 +345,6 @@ export function ContractSearch(props: {
|
|||
<ContractsGrid
|
||||
contracts={hitsByPage[0] === undefined ? undefined : contracts}
|
||||
loadMore={loadMore}
|
||||
hasMore={true}
|
||||
showTime={showTime}
|
||||
onContractClick={onContractClick}
|
||||
overrideGridClassName={overrideGridClassName}
|
||||
|
|
|
@ -5,10 +5,10 @@ import { SiteLink } from '../site-link'
|
|||
import { ContractCard } from './contract-card'
|
||||
import { ShowTime } from './contract-details'
|
||||
import { ContractSearch } from '../contract-search'
|
||||
import { useIsVisible } from 'web/hooks/use-is-visible'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { LoadingIndicator } from '../loading-indicator'
|
||||
import { VisibilityObserver } from '../visibility-observer'
|
||||
|
||||
export type ContractHighlightOptions = {
|
||||
contractIds?: string[]
|
||||
|
@ -17,8 +17,7 @@ export type ContractHighlightOptions = {
|
|||
|
||||
export function ContractsGrid(props: {
|
||||
contracts: Contract[] | undefined
|
||||
loadMore: () => void
|
||||
hasMore: boolean
|
||||
loadMore?: () => void
|
||||
showTime?: ShowTime
|
||||
onContractClick?: (contract: Contract) => void
|
||||
overrideGridClassName?: string
|
||||
|
@ -31,7 +30,6 @@ export function ContractsGrid(props: {
|
|||
const {
|
||||
contracts,
|
||||
showTime,
|
||||
hasMore,
|
||||
loadMore,
|
||||
onContractClick,
|
||||
overrideGridClassName,
|
||||
|
@ -39,16 +37,15 @@ export function ContractsGrid(props: {
|
|||
highlightOptions,
|
||||
} = props
|
||||
const { hideQuickBet, hideGroupLink } = cardHideOptions || {}
|
||||
|
||||
const { contractIds, highlightClassName } = highlightOptions || {}
|
||||
const [elem, setElem] = useState<HTMLElement | null>(null)
|
||||
const isBottomVisible = useIsVisible(elem)
|
||||
|
||||
useEffect(() => {
|
||||
if (isBottomVisible && hasMore) {
|
||||
const onVisibilityUpdated = useCallback(
|
||||
(visible) => {
|
||||
if (visible && loadMore) {
|
||||
loadMore()
|
||||
}
|
||||
}, [isBottomVisible, hasMore, loadMore])
|
||||
},
|
||||
[loadMore]
|
||||
)
|
||||
|
||||
if (contracts === undefined) {
|
||||
return <LoadingIndicator />
|
||||
|
@ -92,7 +89,10 @@ export function ContractsGrid(props: {
|
|||
/>
|
||||
))}
|
||||
</ul>
|
||||
<div ref={setElem} className="relative -top-96 h-1" />
|
||||
<VisibilityObserver
|
||||
onVisibilityUpdated={onVisibilityUpdated}
|
||||
className="relative -top-96 h-1"
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// From https://tailwindui.com/components/application-ui/lists/feeds
|
||||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
import {
|
||||
BanIcon,
|
||||
CheckIcon,
|
||||
|
@ -22,7 +22,6 @@ import { UserLink } from '../user-page'
|
|||
import BetRow from '../bet-row'
|
||||
import { Avatar } from '../avatar'
|
||||
import { ActivityItem } from './activity-items'
|
||||
import { useSaveSeenContract } from 'web/hooks/use-seen-contracts'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { trackClick } from 'web/lib/firebase/tracking'
|
||||
import { DAY_MS } from 'common/util/time'
|
||||
|
@ -50,11 +49,8 @@ export function FeedItems(props: {
|
|||
const { contract, items, className, betRowClassName, user } = props
|
||||
const { outcomeType } = contract
|
||||
|
||||
const [elem, setElem] = useState<HTMLElement | null>(null)
|
||||
useSaveSeenContract(elem, contract)
|
||||
|
||||
return (
|
||||
<div className={clsx('flow-root', className)} ref={setElem}>
|
||||
<div className={clsx('flow-root', className)}>
|
||||
<div className={clsx(tradingAllowed(contract) ? '' : '-mb-6')}>
|
||||
{items.map((item, activityItemIdx) => (
|
||||
<div key={item.id} className={'relative pb-4'}>
|
||||
|
|
|
@ -59,11 +59,7 @@ export function LandingPagePanel(props: { hotContracts: Contract[] }) {
|
|||
<SparklesIcon className="inline h-5 w-5" aria-hidden="true" />
|
||||
Trending markets
|
||||
</Row>
|
||||
<ContractsGrid
|
||||
contracts={hotContracts?.slice(0, 10) || []}
|
||||
loadMore={() => {}}
|
||||
hasMore={false}
|
||||
/>
|
||||
<ContractsGrid contracts={hotContracts?.slice(0, 10) || []} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
24
web/components/visibility-observer.tsx
Normal file
24
web/components/visibility-observer.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useEvent } from '../hooks/use-event'
|
||||
|
||||
export function VisibilityObserver(props: {
|
||||
className?: string
|
||||
onVisibilityUpdated: (visible: boolean) => void
|
||||
}) {
|
||||
const { className } = props
|
||||
const [elem, setElem] = useState<HTMLElement | null>(null)
|
||||
const onVisibilityUpdated = useEvent(props.onVisibilityUpdated)
|
||||
|
||||
useEffect(() => {
|
||||
const hasIOSupport = !!window.IntersectionObserver
|
||||
if (!hasIOSupport || !elem) return
|
||||
|
||||
const observer = new IntersectionObserver(([entry]) => {
|
||||
onVisibilityUpdated(entry.isIntersecting)
|
||||
}, {})
|
||||
observer.observe(elem)
|
||||
return () => observer.disconnect()
|
||||
}, [elem, onVisibilityUpdated])
|
||||
|
||||
return <div ref={setElem} className={className}></div>
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
|
||||
export function useIsVisible(element: HTMLElement | null) {
|
||||
return !!useIntersectionObserver(element)?.isIntersecting
|
||||
}
|
||||
|
||||
function useIntersectionObserver(
|
||||
elem: HTMLElement | null
|
||||
): IntersectionObserverEntry | undefined {
|
||||
const [entry, setEntry] = useState<IntersectionObserverEntry>()
|
||||
|
||||
const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
|
||||
setEntry(entry)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const hasIOSupport = !!window.IntersectionObserver
|
||||
|
||||
if (!hasIOSupport || !elem) return
|
||||
|
||||
const observer = new IntersectionObserver(updateEntry, {})
|
||||
observer.observe(elem)
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [elem])
|
||||
|
||||
return entry
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import { mapValues } from 'lodash'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Contract } from 'common/contract'
|
||||
import { trackView } from 'web/lib/firebase/tracking'
|
||||
import { useIsVisible } from './use-is-visible'
|
||||
import { useUser } from './use-user'
|
||||
|
||||
export const useSeenContracts = () => {
|
||||
const [seenContracts, setSeenContracts] = useState<{
|
||||
[contractId: string]: number
|
||||
}>({})
|
||||
|
||||
useEffect(() => {
|
||||
setSeenContracts(getSeenContracts())
|
||||
}, [])
|
||||
|
||||
return seenContracts
|
||||
}
|
||||
|
||||
export const useSaveSeenContract = (
|
||||
elem: HTMLElement | null,
|
||||
contract: Contract
|
||||
) => {
|
||||
const isVisible = useIsVisible(elem)
|
||||
const user = useUser()
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible && user) {
|
||||
const newSeenContracts = {
|
||||
...getSeenContracts(),
|
||||
[contract.id]: Date.now(),
|
||||
}
|
||||
localStorage.setItem(key, JSON.stringify(newSeenContracts))
|
||||
|
||||
trackView(user.id, contract.id)
|
||||
}
|
||||
}, [isVisible, user, contract])
|
||||
}
|
||||
|
||||
const key = 'feed-seen-contracts'
|
||||
|
||||
const getSeenContracts = () => {
|
||||
return mapValues(
|
||||
JSON.parse(localStorage.getItem(key) ?? '{}'),
|
||||
(time) => +time
|
||||
)
|
||||
}
|
|
@ -110,12 +110,7 @@ export default function ContractSearchFirestore(props: {
|
|||
<option value="close-date">Closing soon</option>
|
||||
</select>
|
||||
</div>
|
||||
<ContractsGrid
|
||||
contracts={matches}
|
||||
loadMore={() => {}}
|
||||
hasMore={false}
|
||||
showTime={showTime}
|
||||
/>
|
||||
<ContractsGrid contracts={matches} showTime={showTime} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user