From 33539c291b419ecc7be979d8972f3408fa1f2cff Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Dec 2025 14:04:09 +0000 Subject: [PATCH] refactor: use applesauce helpers for pointer parsing and filter comparison Phase 2 & 3 of applesauce helpers refactoring plan. **Phase 2: Replace manual pointer parsing** - ReactionRenderer.tsx: replaced manual coordinate parsing with parseCoordinate helper - Benefits: more robust, handles edge cases, consistent with applesauce patterns **Phase 3: Improve filter comparison** - useStable.ts: replaced JSON.stringify with isFilterEqual for filter comparison - Benefits: handles undefined values correctly, supports NIP-ND AND operator - Implementation uses ref pattern to maintain stable reference when filters are equal All tests pass (607 tests). --- .../nostr/kinds/ReactionRenderer.tsx | 23 +++++++----------- src/hooks/useStable.ts | 24 ++++++++++++++----- 2 files changed, 26 insertions(+), 21 deletions(-) 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; }