diff --git a/src/components/nostr/kinds/ReactionRenderer.tsx b/src/components/nostr/kinds/ReactionRenderer.tsx index 52939f1..dfdcd4c 100644 --- a/src/components/nostr/kinds/ReactionRenderer.tsx +++ b/src/components/nostr/kinds/ReactionRenderer.tsx @@ -5,6 +5,7 @@ import { useMemo } from "react"; import { NostrEvent } from "@/types/nostr"; import { KindRenderer } from "./index"; import { EventCardSkeleton } from "@/components/ui/skeleton"; +import { parseCoordinate } from "applesauce-core/helpers/pointers"; /** * Renderer for Kind 7 - Reactions @@ -54,16 +55,8 @@ export function Kind7Renderer({ event }: BaseEventProps) { const aTag = event.tags.find((tag) => tag[0] === "a"); const reactedAddress = aTag?.[1]; // Format: kind:pubkey:d-tag - // Parse a tag into components - const addressParts = useMemo(() => { - if (!reactedAddress) return null; - const parts = reactedAddress.split(":"); - return { - kind: parseInt(parts[0], 10), - pubkey: parts[1], - dTag: parts[2], - }; - }, [reactedAddress]); + // Parse a tag coordinate using applesauce helper + const addressPointer = reactedAddress ? parseCoordinate(reactedAddress) : null; // Create event pointer for fetching const eventPointer = useMemo(() => { @@ -73,16 +66,16 @@ export function Kind7Renderer({ event }: BaseEventProps) { relays: reactedRelay ? [reactedRelay] : undefined, }; } - if (addressParts) { + if (addressPointer) { return { - kind: addressParts.kind, - pubkey: addressParts.pubkey, - identifier: addressParts.dTag || "", + kind: addressPointer.kind, + pubkey: addressPointer.pubkey, + identifier: addressPointer.identifier || "", relays: [], }; } return undefined; - }, [reactedEventId, reactedRelay, addressParts]); + }, [reactedEventId, reactedRelay, addressPointer]); // Fetch the reacted event const reactedEvent = useNostrEvent(eventPointer); diff --git a/src/hooks/useStable.ts b/src/hooks/useStable.ts index 29610e2..82d95ac 100644 --- a/src/hooks/useStable.ts +++ b/src/hooks/useStable.ts @@ -1,4 +1,6 @@ -import { useMemo } from "react"; +import { useMemo, useRef } from "react"; +import { isFilterEqual } from "applesauce-core/helpers/filter"; +import type { Filter } from "nostr-tools"; /** * Stabilize a value for use in dependency arrays @@ -46,13 +48,23 @@ export function useStableArray(arr: T[]): T[] { /** * Stabilize a Nostr filter or array of filters * - * Specialized stabilizer for Nostr filters which are commonly - * recreated on each render. + * Uses applesauce's isFilterEqual for robust filter comparison. + * Better than JSON.stringify as it handles undefined values correctly + * and supports NIP-ND AND operator. * * @param filters - Single filter or array of filters * @returns The memoized filter(s) */ -export function useStableFilters(filters: T): T { - // eslint-disable-next-line react-hooks/exhaustive-deps - return useMemo(() => filters, [JSON.stringify(filters)]); +export function useStableFilters(filters: T): T { + const prevFiltersRef = useRef(); + + // Only update if filters actually changed (per isFilterEqual) + if ( + !prevFiltersRef.current || + !isFilterEqual(prevFiltersRef.current as Filter | Filter[], filters as Filter | Filter[]) + ) { + prevFiltersRef.current = filters; + } + + return prevFiltersRef.current; }