df5c08ef27
Several mutations published Nostr events but invalidated cache keys that no live query subscribed to, leaving the UI showing stale counts and missing entries until the user manually refreshed. * Campaign donations: useDonateCampaign and CampaignDetailPage invalidated ['campaign-donations', aTag], but useCampaignDonations subscribes to ['campaign-donations', 'events', aTag]. Use the correct key, cascade to organization-activity / campaigns lists, and broaden via prefix sweep. * Reactions / reposts / quotes: ReactionButton, RepostMenu, QuickReactMenu, ComposeBox quote path, VinesFeedPage and ListDetailPage all wrote to ['event-stats', id], which no query reads. Counts are served by useNip85EventStats / useNip85AddrStats at ['nip85-event-stats', id, statsPubkey] and ['nip85-addr-stats', addr, statsPubkey]. Route optimistic writes and invalidations through a new invalidateEventStats helper that handles both the regular and addressable variants. * Top-level posts on country pages: ComposeBox's createEvent path invalidated ['feed'], but country pages subscribe to ['agora-feed-paginated', countryCode, ...] and ['agora-feed-new-posts', countryCode, ...]. Add the country-feed invalidations to the kind 1, voice, and poll handlers — matching the pattern usePostComment already uses for kind 1111 comments. * Follow All inline reimplementations: FollowPage's FollowPackView, TeamSoapboxCard, and FollowPackDetailContent published kind 3 events inline with no invalidation, so follow buttons and the user's feed stayed unchanged. Replace each with useFollowActions.followMany, which already invalidates ['follow-list'], ['feed'], and ['following-feed'].
48 lines
2.0 KiB
TypeScript
48 lines
2.0 KiB
TypeScript
import type { QueryClient } from '@tanstack/react-query';
|
||
import type { NostrEvent } from '@nostrify/nostrify';
|
||
|
||
/**
|
||
* Invalidate the NIP-85 stats queries for a given event so that
|
||
* reaction / repost / comment / zap counts update in the UI after the
|
||
* user performs an action against it.
|
||
*
|
||
* Handles both regular events (`['nip85-event-stats', eventId, statsPubkey]`)
|
||
* and addressable events (`['nip85-addr-stats', '<kind>:<pubkey>:<d>', statsPubkey]`).
|
||
*
|
||
* Pass `statsPubkey` from `useAppContext().config.nip85StatsPubkey`. When the
|
||
* configured pubkey isn't known, the function still issues a prefix-match
|
||
* invalidation so any cached entry refreshes.
|
||
*
|
||
* `eventOrId` accepts either a hex event id (regular event) or a full
|
||
* `NostrEvent`. Passing the event allows us to detect addressable kinds
|
||
* (10000–19999, 30000–39999) and invalidate the matching `nip85-addr-stats`
|
||
* key too.
|
||
*/
|
||
export function invalidateEventStats(
|
||
queryClient: QueryClient,
|
||
eventOrId: NostrEvent | string,
|
||
statsPubkey?: string,
|
||
): void {
|
||
if (typeof eventOrId === 'string') {
|
||
queryClient.invalidateQueries({ queryKey: ['nip85-event-stats', eventOrId, statsPubkey] });
|
||
// Also a prefix sweep, in case statsPubkey was undefined when the cache
|
||
// entry was created (e.g. early load before config hydration).
|
||
queryClient.invalidateQueries({ queryKey: ['nip85-event-stats', eventOrId] });
|
||
return;
|
||
}
|
||
|
||
const event = eventOrId;
|
||
queryClient.invalidateQueries({ queryKey: ['nip85-event-stats', event.id, statsPubkey] });
|
||
queryClient.invalidateQueries({ queryKey: ['nip85-event-stats', event.id] });
|
||
|
||
const isAddressable =
|
||
(event.kind >= 30000 && event.kind < 40000) ||
|
||
(event.kind >= 10000 && event.kind < 20000);
|
||
if (isAddressable) {
|
||
const d = event.tags.find(([n]) => n === 'd')?.[1] ?? '';
|
||
const addr = `${event.kind}:${event.pubkey}:${d}`;
|
||
queryClient.invalidateQueries({ queryKey: ['nip85-addr-stats', addr, statsPubkey] });
|
||
queryClient.invalidateQueries({ queryKey: ['nip85-addr-stats', addr] });
|
||
}
|
||
}
|