From fe7cdc3eff64a4414921f0908ba061ef7fc6152d Mon Sep 17 00:00:00 2001 From: Chad Curtis Date: Sun, 1 Mar 2026 02:40:44 -0600 Subject: [PATCH] Batch kinds 0, 3, 10000 into single REQ in useFollowList --- src/hooks/useFollowActions.ts | 48 ++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/src/hooks/useFollowActions.ts b/src/hooks/useFollowActions.ts index 7443756d..892e793b 100644 --- a/src/hooks/useFollowActions.ts +++ b/src/hooks/useFollowActions.ts @@ -3,6 +3,8 @@ import { useNostr } from '@nostrify/react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useCurrentUser } from './useCurrentUser'; import { useNostrPublish } from './useNostrPublish'; +import { parseAuthorEvent } from './useAuthor'; +import { parseMuteTags, setCachedMuteItems } from './useMuteList'; import type { NostrEvent } from '@nostrify/nostrify'; /** @@ -57,7 +59,7 @@ function getCachedFollowList(pubkey: string): FollowListData | undefined { } /** Persist follow pubkeys to localStorage. */ -function setCachedFollowList(pubkey: string, pubkeys: string[]): void { +export function setCachedFollowList(pubkey: string, pubkeys: string[]): void { try { localStorage.setItem(FOLLOW_CACHE_KEY, JSON.stringify({ pubkey, pubkeys })); } catch { @@ -76,22 +78,54 @@ function setCachedFollowList(pubkey: string, pubkeys: string[]): void { export function useFollowList() { const { nostr } = useNostr(); const { user } = useCurrentUser(); + const queryClient = useQueryClient(); return useQuery({ queryKey: ['follow-list', user?.pubkey ?? ''], queryFn: async ({ signal }) => { if (!user) return { event: null, pubkeys: [] }; - const [event] = await nostr.query( - [{ kinds: [3], authors: [user.pubkey], limit: 1 }], - { signal: AbortSignal.any([signal, AbortSignal.timeout(3000)]) }, + + // Fetch profile, follow list, and mute list in one REQ so layout + // components (useCurrentUser, useMuteList) resolve from cache + // instead of each firing their own query. + const events = await nostr.query( + [{ kinds: [0, 3, 10000], authors: [user.pubkey], limit: 3 }], + { signal: AbortSignal.any([signal, AbortSignal.timeout(5000)]) }, ); - if (!event) return { event: null, pubkeys: [] }; - const pubkeys = event.tags + + const profile = events.find((e) => e.kind === 0); + const followList = events.find((e) => e.kind === 3); + const muteList = events.find((e) => e.kind === 10000); + + // Seed author cache so useCurrentUser resolves instantly + if (profile) { + queryClient.setQueryData(['author', user.pubkey], parseAuthorEvent(profile)); + } + + // Seed mute list cache so useMuteList resolves instantly + if (muteList) { + queryClient.setQueryData(['muteList', user.pubkey], muteList); + if (muteList.content && user.signer.nip44) { + try { + const decrypted = await user.signer.nip44.decrypt(user.pubkey, muteList.content); + const tags = JSON.parse(decrypted) as string[][]; + const items = parseMuteTags(tags); + queryClient.setQueryData(['muteItems', muteList.id], items); + setCachedMuteItems(user.pubkey, items); + } catch { + // Decryption failure is non-critical — useMuteList will retry + } + } + } + + if (!followList) return { event: null, pubkeys: [] }; + + const pubkeys = followList.tags .filter(([name]) => name === 'p') .map(([, pk]) => pk); // Persist to localStorage for next visit setCachedFollowList(user.pubkey, pubkeys); - return { event, pubkeys }; + return { event: followList, pubkeys }; }, enabled: !!user, staleTime: 5 * 60 * 1000,