From d52cfa5f75ff6501585bf7c1fe77cd3251e4c68e Mon Sep 17 00:00:00 2001 From: Jure <44338+hoornet@users.noreply.github.com> Date: Mon, 9 Mar 2026 17:31:42 +0100 Subject: [PATCH] Add network reaction counts to note cards - fetchReactionCount (kind 7 #e filter) in nostr lib - useReactionCount hook with module-level cache to avoid refetching - NoteCard shows count next to like button; increments optimistically on like - Falls back to "like"/"liked" text when count is zero or still loading Co-Authored-By: Claude Sonnet 4.6 --- src/components/feed/NoteCard.tsx | 5 ++++- src/hooks/useReactionCount.ts | 29 +++++++++++++++++++++++++++++ src/lib/nostr/client.ts | 12 ++++++++++++ src/lib/nostr/index.ts | 2 +- 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/hooks/useReactionCount.ts diff --git a/src/components/feed/NoteCard.tsx b/src/components/feed/NoteCard.tsx index 553bf34..66f6963 100644 --- a/src/components/feed/NoteCard.tsx +++ b/src/components/feed/NoteCard.tsx @@ -1,6 +1,7 @@ import { useState, useRef } from "react"; import { NDKEvent } from "@nostr-dev-kit/ndk"; import { useProfile } from "../../hooks/useProfile"; +import { useReactionCount } from "../../hooks/useReactionCount"; import { useUserStore } from "../../stores/user"; import { useUIStore } from "../../stores/ui"; import { timeAgo, shortenPubkey } from "../../lib/utils"; @@ -27,6 +28,7 @@ export function NoteCard({ event }: NoteCardProps) { }; const [liked, setLiked] = useState(() => getLiked().has(event.id)); const [liking, setLiking] = useState(false); + const [reactionCount, adjustReactionCount] = useReactionCount(event.id); const [showReply, setShowReply] = useState(false); const [replyText, setReplyText] = useState(""); const [replying, setReplying] = useState(false); @@ -43,6 +45,7 @@ export function NoteCard({ event }: NoteCardProps) { liked.add(event.id); localStorage.setItem(likedKey, JSON.stringify(Array.from(liked))); setLiked(true); + adjustReactionCount(1); } finally { setLiking(false); } @@ -134,7 +137,7 @@ export function NoteCard({ event }: NoteCardProps) { liked ? "text-accent" : "text-text-dim hover:text-accent" } disabled:cursor-default`} > - {liked ? "♥ liked" : "♡ like"} + {liked ? "♥" : "♡"}{reactionCount !== null && reactionCount > 0 ? ` ${reactionCount}` : liked ? " liked" : " like"} )} diff --git a/src/hooks/useReactionCount.ts b/src/hooks/useReactionCount.ts new file mode 100644 index 0000000..85e110b --- /dev/null +++ b/src/hooks/useReactionCount.ts @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; +import { fetchReactionCount } from "../lib/nostr"; + +const cache = new Map(); + +export function useReactionCount(eventId: string): [number | null, (delta: number) => void] { + const [count, setCount] = useState(() => cache.get(eventId) ?? null); + + useEffect(() => { + if (cache.has(eventId)) { + setCount(cache.get(eventId)!); + return; + } + fetchReactionCount(eventId).then((n) => { + cache.set(eventId, n); + setCount(n); + }); + }, [eventId]); + + const adjust = (delta: number) => { + setCount((prev) => { + const next = (prev ?? 0) + delta; + cache.set(eventId, next); + return next; + }); + }; + + return [count, adjust]; +} diff --git a/src/lib/nostr/client.ts b/src/lib/nostr/client.ts index 3b80533..9894508 100644 --- a/src/lib/nostr/client.ts +++ b/src/lib/nostr/client.ts @@ -191,6 +191,18 @@ export async function fetchUserNotes(pubkey: string, limit = 30): Promise (b.created_at ?? 0) - (a.created_at ?? 0)); } +export async function fetchReactionCount(eventId: string): Promise { + const instance = getNDK(); + const filter: NDKFilter = { + kinds: [NDKKind.Reaction], + "#e": [eventId], + }; + const events = await instance.fetchEvents(filter, { + cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY, + }); + return events.size; +} + export async function publishContactList(pubkeys: string[]): Promise { const instance = getNDK(); if (!instance.signer) throw new Error("Not logged in"); diff --git a/src/lib/nostr/index.ts b/src/lib/nostr/index.ts index e6caab1..3a9b0e6 100644 --- a/src/lib/nostr/index.ts +++ b/src/lib/nostr/index.ts @@ -1 +1 @@ -export { getNDK, connectToRelays, fetchGlobalFeed, fetchFollowFeed, fetchReplies, publishNote, publishArticle, publishProfile, publishReaction, publishReply, publishContactList, fetchUserNotes, fetchProfile } from "./client"; +export { getNDK, connectToRelays, fetchGlobalFeed, fetchFollowFeed, fetchReplies, publishNote, publishArticle, publishProfile, publishReaction, publishReply, publishContactList, fetchReactionCount, fetchUserNotes, fetchProfile } from "./client";