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).
This commit is contained in:
Claude
2025-12-22 14:04:09 +00:00
parent 4b7148510a
commit 33539c291b
2 changed files with 26 additions and 21 deletions

View File

@@ -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);

View File

@@ -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<T extends string>(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<T>(filters: T): T {
// eslint-disable-next-line react-hooks/exhaustive-deps
return useMemo(() => filters, [JSON.stringify(filters)]);
export function useStableFilters<T extends Filter | Filter[]>(filters: T): T {
const prevFiltersRef = useRef<T>();
// 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;
}