@@ -212,7 +232,7 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
Open in Lightning Wallet
-
@@ -432,9 +452,8 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
- Zaps are small Bitcoin payments that support the creator of this item.
- {' '}If you enjoyed this, consider sending a zap!
+
+ Zaps are small Bitcoin payments that support the creator of this item. If you enjoyed this, consider sending a zap!
@@ -457,13 +476,12 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
{invoice ? 'Lightning Payment' : 'Send a Zap'}
-
+
{invoice ? (
'Pay with Bitcoin Lightning Network'
) : (
<>
- Zaps are small Bitcoin payments that support the creator of this item.
- {' '}If you enjoyed this, consider sending a zap!
+ Zaps are small Bitcoin payments that support the creator of this item. If you enjoyed this, consider sending a zap!
>
)}
diff --git a/src/hooks/useNWC.ts b/src/hooks/useNWC.ts
index f11e63a..5a7c370 100644
--- a/src/hooks/useNWC.ts
+++ b/src/hooks/useNWC.ts
@@ -66,6 +66,7 @@ export function useNWCInternal() {
try {
// Test the connection by creating an LN client with timeout
+ let timeoutId: NodeJS.Timeout | undefined;
const testPromise = new Promise((resolve, reject) => {
try {
const client = new LN(parsed.connectionString);
@@ -74,10 +75,17 @@ export function useNWCInternal() {
reject(error);
}
});
- const timeoutPromise = new Promise((_, reject) => {
- setTimeout(() => reject(new Error('Connection test timeout')), 10000);
+ const timeoutPromise = new Promise((_, reject) => {
+ timeoutId = setTimeout(() => reject(new Error('Connection test timeout')), 10000);
});
- await Promise.race([testPromise, timeoutPromise]) as LN;
+
+ try {
+ await Promise.race([testPromise, timeoutPromise]) as LN;
+ if (timeoutId) clearTimeout(timeoutId);
+ } catch (error) {
+ if (timeoutId) clearTimeout(timeoutId);
+ throw error;
+ }
const connection: NWCConnection = {
connectionString: parsed.connectionString,
@@ -149,7 +157,7 @@ export function useNWCInternal() {
setActiveConnection(connections[0].connectionString);
return connections[0];
}
-
+
if (!activeConnection) return null;
const found = connections.find(c => c.connectionString === activeConnection);
@@ -175,14 +183,22 @@ export function useNWCInternal() {
}
try {
- // Add timeout to prevent hanging
- const timeoutPromise = new Promise((_, reject) => {
- setTimeout(() => reject(new Error('Payment timeout after 15 seconds')), 15000);
+ // Add timeout to prevent hanging with proper cleanup
+ let timeoutId: NodeJS.Timeout | undefined;
+ const timeoutPromise = new Promise((_, reject) => {
+ timeoutId = setTimeout(() => reject(new Error('Payment timeout after 15 seconds')), 15000);
});
const paymentPromise = client.pay(invoice);
- const response = await Promise.race([paymentPromise, timeoutPromise]) as { preimage: string };
- return response;
+
+ try {
+ const response = await Promise.race([paymentPromise, timeoutPromise]) as { preimage: string };
+ if (timeoutId) clearTimeout(timeoutId);
+ return response;
+ } catch (error) {
+ if (timeoutId) clearTimeout(timeoutId);
+ throw error;
+ }
} catch (error) {
console.error('NWC payment failed:', error);
@@ -222,6 +238,7 @@ export function useNWCInternal() {
try {
// Create a fresh client for testing
+ let timeoutId: NodeJS.Timeout | undefined;
const testPromise = new Promise((resolve, reject) => {
try {
const client = new LN(connection.connectionString);
@@ -231,10 +248,17 @@ export function useNWCInternal() {
}
});
- const timeoutPromise = new Promise((_, reject) => {
- setTimeout(() => reject(new Error('Connection test timeout')), 5000);
+ const timeoutPromise = new Promise((_, reject) => {
+ timeoutId = setTimeout(() => reject(new Error('Connection test timeout')), 5000);
});
- await Promise.race([testPromise, timeoutPromise]);
+
+ try {
+ await Promise.race([testPromise, timeoutPromise]);
+ if (timeoutId) clearTimeout(timeoutId);
+ } catch (error) {
+ if (timeoutId) clearTimeout(timeoutId);
+ throw error;
+ }
return true;
} catch (error) {
diff --git a/src/hooks/useZaps.ts b/src/hooks/useZaps.ts
index 36840df..9589449 100644
--- a/src/hooks/useZaps.ts
+++ b/src/hooks/useZaps.ts
@@ -1,4 +1,4 @@
-import { useState, useMemo } from 'react';
+import { useState, useMemo, useEffect } from 'react';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useAuthor } from '@/hooks/useAuthor';
import { useAppContext } from '@/hooks/useAppContext';
@@ -32,10 +32,21 @@ export function useZaps(
const [isZapping, setIsZapping] = useState(false);
const [invoice, setInvoice] = useState(null);
+ // Cleanup state when component unmounts
+ useEffect(() => {
+ return () => {
+ setIsZapping(false);
+ setInvoice(null);
+ };
+ }, []);
+
const { data: zapEvents, ...query } = useQuery({
queryKey: ['zaps', actualTarget?.id],
staleTime: 30000, // 30 seconds
- refetchInterval: 60000, // Refetch every minute to catch new zaps
+ refetchInterval: (query) => {
+ // Only refetch if the query is currently being observed (component is mounted)
+ return query.getObserversCount() > 0 ? 60000 : false;
+ },
queryFn: async (c) => {
if (!actualTarget) return [];
@@ -64,7 +75,7 @@ export function useZaps(
// Process zap events into simple counts and totals
const { zapCount, totalSats, zaps } = useMemo(() => {
- if (!zapEvents || !actualTarget) {
+ if (!zapEvents || !Array.isArray(zapEvents) || !actualTarget) {
return { zapCount: 0, totalSats: 0, zaps: [] };
}