From 1b9688961441bf07def48aad3d901ce3e107684b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 21 Feb 2026 22:23:23 -0600 Subject: [PATCH] Consolidate duplicate NIP-85 stat fetching into single hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useEventStats now consumes useNip85EventStats instead of having its own inline NIP-85 query. This eliminates duplicate kind 30383 queries for every post on the feed — both hooks now share the same TanStack Query cache entry. --- src/hooks/useTrending.ts | 47 ++++++++++------------------------------ src/lib/NostrBatcher.ts | 2 +- 2 files changed, 12 insertions(+), 37 deletions(-) diff --git a/src/hooks/useTrending.ts b/src/hooks/useTrending.ts index 98720fec..2ee45934 100644 --- a/src/hooks/useTrending.ts +++ b/src/hooks/useTrending.ts @@ -2,6 +2,7 @@ import { useNostr } from '@nostrify/react'; import { useQuery } from '@tanstack/react-query'; import type { NostrEvent, NostrFilter } from '@nostrify/nostrify'; import { useAppContext } from '@/hooks/useAppContext'; +import { useNip85EventStats } from '@/hooks/useNip85Stats'; import { type ResolvedEmoji, isCustomEmoji, getCustomEmojiUrl } from '@/components/CustomEmoji'; /** The sole relay used for trend data. */ @@ -269,49 +270,23 @@ function computeStats(eventId: string, events: NostrEvent[]): EventStats { export function useEventStats(eventId: string | undefined) { const { nostr } = useNostr(); const { config } = useAppContext(); - const statsPubkey = config.nip85StatsPubkey; const nip85OnlyMode = config.nip85OnlyMode; + const nip85 = useNip85EventStats(eventId); return useQuery({ - queryKey: ['event-stats', eventId ?? ''], + queryKey: ['event-stats', eventId ?? '', !!nip85.data], queryFn: async ({ signal }) => { if (!eventId) return EMPTY_STATS; - // Try NIP-85 first with aggressive timeout (500ms) - let nip85Stats: NostrEvent[] = []; - if (statsPubkey) { - try { - nip85Stats = await nostr.query( - [{ - kinds: [30383], - authors: [statsPubkey], - '#d': [eventId], - limit: 1, - }], - { signal: AbortSignal.any([signal, AbortSignal.timeout(500)]) }, - ); - } catch { - // NIP-85 failed or timed out - } - } - - const hasNip85 = nip85Stats.length > 0; - // If we have NIP-85 stats, use them directly — no second query needed. - if (hasNip85) { - const event = nip85Stats[0]; - const getTagValue = (tagName: string): number => { - const tag = event.tags.find(([name]) => name === tagName); - return tag?.[1] ? parseInt(tag[1], 10) : 0; - }; - + if (nip85.data) { return { - replies: getTagValue('comment_cnt'), - reposts: getTagValue('repost_cnt'), - quotes: getTagValue('quote_cnt'), - reactions: getTagValue('reaction_cnt'), - zapAmount: getTagValue('zap_amount'), - zapCount: getTagValue('zap_cnt'), + replies: nip85.data.commentCount, + reposts: nip85.data.repostCount, + quotes: 0, + reactions: nip85.data.reactionCount, + zapAmount: 0, + zapCount: nip85.data.zapCount, reactionEmojis: [], }; } @@ -334,7 +309,7 @@ export function useEventStats(eventId: string | undefined) { return computeStats(eventId, events); }, - enabled: !!eventId, + enabled: !!eventId && !nip85.isLoading, staleTime: 60 * 1000, placeholderData: (prev) => prev, }); diff --git a/src/lib/NostrBatcher.ts b/src/lib/NostrBatcher.ts index 1aedbbac..7bd6768e 100644 --- a/src/lib/NostrBatcher.ts +++ b/src/lib/NostrBatcher.ts @@ -337,7 +337,7 @@ export class NostrBatcher { const multiFilterEventId = isMultiFilterETagBatchable(filters); if (multiFilterEventId !== null) { // Serialize the filter "shape" (kinds, tag names, limits) to get a collector key. - // Different useEventStats calls with the same hasNip85 value produce identical shapes. + // Multi-filter queries with the same shape are batched together. const shapeKey = filters.map((f) => { const keys = Object.keys(f).sort(); return keys.map((k) => k === '#e' || k === '#q' ? k : `${k}:${JSON.stringify((f as Record)[k])}`).join('|');