Browse Source

fix race condition

imwald
Silberengel 1 month ago
parent
commit
ecc5aab6b2
  1. 37
      src/components/Explore/ExploreRelayReviews.tsx
  2. 7
      src/components/RelayIcon/index.tsx
  3. 2
      src/components/RelayInfo/RelayReviewCard.tsx
  4. 6
      src/hooks/useFetchRelayInfo.tsx

37
src/components/Explore/ExploreRelayReviews.tsx

@ -10,6 +10,7 @@ import {
userReadRelaysWithHttp userReadRelaysWithHttp
} from '@/lib/favorites-feed-relays' } from '@/lib/favorites-feed-relays'
import { toRelay } from '@/lib/link' import { toRelay } from '@/lib/link'
import { normalizeAnyRelayUrl } from '@/lib/url'
import { appendCuratedReadOnlyRelays } from '@/pages/primary/SpellsPage/fauxSpellFeeds' import { appendCuratedReadOnlyRelays } from '@/pages/primary/SpellsPage/fauxSpellFeeds'
import { useSmartRelayNavigation } from '@/PageManager' import { useSmartRelayNavigation } from '@/PageManager'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
@ -29,7 +30,7 @@ function RelayGroupHeader({ url, reviewCount }: { url: string; reviewCount: numb
className="flex w-full min-w-0 items-center gap-2 px-4 md:px-4 pt-4 pb-2 border-b text-left hover:opacity-75 transition-opacity" className="flex w-full min-w-0 items-center gap-2 px-4 md:px-4 pt-4 pb-2 border-b text-left hover:opacity-75 transition-opacity"
onClick={() => navigateToRelay(toRelay(url))} onClick={() => navigateToRelay(toRelay(url))}
> >
<RelayIcon url={url} className="h-8 w-8 shrink-0 rounded-sm" iconSize={16} /> <RelayIcon url={url} skipRelayInfoFetch className="h-8 w-8 shrink-0 rounded-sm" iconSize={16} />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
{relayInfo?.name && ( {relayInfo?.name && (
<div className="truncate font-semibold text-sm leading-tight">{relayInfo.name}</div> <div className="truncate font-semibold text-sm leading-tight">{relayInfo.name}</div>
@ -87,11 +88,35 @@ async function loadCachedRelayReviews(limit: number): Promise<Event[]> {
} }
} }
function stableRelayInputsKey(
favoriteRelays: string[],
blockedRelays: string[],
relayList: { read?: string[]; write?: string[]; httpRead?: string[] } | null | undefined
): string {
const normSortJoin = (urls: string[]) =>
[...urls]
.map((u) => normalizeAnyRelayUrl(u) || u.trim())
.filter(Boolean)
.sort((a, b) => a.localeCompare(b))
.join('|')
return [
normSortJoin(favoriteRelays),
normSortJoin(blockedRelays),
normSortJoin([...(relayList?.httpRead ?? []), ...(relayList?.read ?? [])]),
normSortJoin(relayList?.write ?? [])
].join('::')
}
export default function ExploreRelayReviews() { export default function ExploreRelayReviews() {
const { t } = useTranslation() const { t } = useTranslation()
const { favoriteRelays, blockedRelays } = useFavoriteRelays() const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const { relayList } = useNostr() const { relayList } = useNostr()
const relayInputsKey = useMemo(
() => stableRelayInputsKey(favoriteRelays, blockedRelays, relayList),
[favoriteRelays, blockedRelays, relayList]
)
const relayUrls = useMemo(() => { const relayUrls = useMemo(() => {
const stacked = appendCuratedReadOnlyRelays( const stacked = appendCuratedReadOnlyRelays(
getRelayUrlsWithFavoritesFastReadAndInbox( getRelayUrlsWithFavoritesFastReadAndInbox(
@ -106,10 +131,14 @@ export default function ExploreRelayReviews() {
), ),
blockedRelays blockedRelays
) )
return stacked.slice(0, EXPLORE_REVIEWS_MAX_RELAYS) const sliced = stacked.slice(0, EXPLORE_REVIEWS_MAX_RELAYS)
}, [favoriteRelays, blockedRelays, relayList]) const normalized = sliced.map((u) => normalizeAnyRelayUrl(u) || u.trim()).filter(Boolean)
normalized.sort((a, b) => a.localeCompare(b))
return normalized
// eslint-disable-next-line react-hooks/exhaustive-deps -- relayInputsKey is a content hash of favorites/blocked/NIP-65; relayList identity churn must not re-open REQ sockets.
}, [relayInputsKey])
const relayUrlsKey = useMemo(() => relayUrls.join('|'), [relayUrls]) const relayUrlsKey = relayInputsKey
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [events, setEvents] = useState<Event[]>([]) const [events, setEvents] = useState<Event[]>([])

7
src/components/RelayIcon/index.tsx

@ -32,13 +32,16 @@ function resolveRelayImageUrl(raw: string, relayUrl: string): string | undefined
export default function RelayIcon({ export default function RelayIcon({
url, url,
className, className,
iconSize = 14 iconSize = 14,
/** When true, do not hit NIP-11 (parent already fetches relay info, or icon-only row). */
skipRelayInfoFetch = false
}: { }: {
url?: string url?: string
className?: string className?: string
iconSize?: number iconSize?: number
skipRelayInfoFetch?: boolean
}) { }) {
const { relayInfo } = useFetchRelayInfo(url) const { relayInfo } = useFetchRelayInfo(skipRelayInfoFetch ? undefined : url)
const iconUrl = useMemo(() => { const iconUrl = useMemo(() => {
if (!url) return undefined if (!url) return undefined

2
src/components/RelayInfo/RelayReviewCard.tsx

@ -28,7 +28,7 @@ export default function RelayReviewCard({
const { navigateToRelay } = useSmartRelayNavigation() const { navigateToRelay } = useSmartRelayNavigation()
const stars = useMemo(() => getStarsFromRelayReviewEvent(event), [event]) const stars = useMemo(() => getStarsFromRelayReviewEvent(event), [event])
const relayUrl = useMemo(() => getRelayUrlFromRelayReviewEvent(event), [event]) const relayUrl = useMemo(() => getRelayUrlFromRelayReviewEvent(event), [event])
const { relayInfo } = useFetchRelayInfo(relayUrl) const { relayInfo } = useFetchRelayInfo(showRelayInfo ? relayUrl : undefined)
return ( return (
<div <div

6
src/hooks/useFetchRelayInfo.tsx

@ -8,7 +8,11 @@ export function useFetchRelayInfo(url?: string) {
const [relayInfo, setRelayInfo] = useState<TRelayInfo | undefined>(undefined) const [relayInfo, setRelayInfo] = useState<TRelayInfo | undefined>(undefined)
useEffect(() => { useEffect(() => {
if (!url) return if (!url) {
setRelayInfo(undefined)
setIsFetching(false)
return
}
const fetchRelayInfos = async () => { const fetchRelayInfos = async () => {
setIsFetching(true) setIsFetching(true)
const timer = setTimeout(() => { const timer = setTimeout(() => {

Loading…
Cancel
Save