From 145048df3988011e5eebb7cf276b8866aaccb90a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Jan 2026 17:16:35 +0000 Subject: [PATCH] fix: prevent infinite transaction loading loop in WalletViewer Previously, the transaction list would try to load constantly and fail repeatedly due to a circular dependency in the useEffect hooks. The listTransactions function from useWallet wasn't wrapped in useCallback, causing loadInitialTransactions to be recreated on every render, which triggered the useEffect infinitely. Changes: - Add txLoadAttempted and txLoadFailed flags to prevent repeated attempts - Only attempt to load transactions once on wallet connection - Fail silently on initial load (no toast spam) - Show retry button when transaction loading fails - Reset flags when wallet connects/disconnects or after successful payments - Make transaction list truly optional - wallet still works if loading fails This ensures a better UX when wallets don't support list_transactions or when the method fails for any reason. --- src/components/WalletViewer.tsx | 52 +++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/src/components/WalletViewer.tsx b/src/components/WalletViewer.tsx index 213d1a8..4685005 100644 --- a/src/components/WalletViewer.tsx +++ b/src/components/WalletViewer.tsx @@ -206,6 +206,8 @@ export default function WalletViewer() { const [hasMore, setHasMore] = useState(true); const [connectDialogOpen, setConnectDialogOpen] = useState(false); const [disconnectDialogOpen, setDisconnectDialogOpen] = useState(false); + const [txLoadAttempted, setTxLoadAttempted] = useState(false); + const [txLoadFailed, setTxLoadFailed] = useState(false); // Send dialog state const [sendDialogOpen, setSendDialogOpen] = useState(false); @@ -245,7 +247,11 @@ export default function WalletViewer() { }, [getInfo]); const loadInitialTransactions = useCallback(async () => { + // Prevent repeated attempts if already failed + if (txLoadFailed) return; + setLoading(true); + setTxLoadAttempted(true); try { const result = await listTransactions({ limit: BATCH_SIZE, @@ -254,26 +260,37 @@ export default function WalletViewer() { const txs = result.transactions || []; setTransactions(txs); setHasMore(txs.length === BATCH_SIZE); + setTxLoadFailed(false); } catch (error) { console.error("Failed to load transactions:", error); - toast.error("Failed to load transactions"); + setTxLoadFailed(true); + // Don't show toast on initial load - just fail silently + // User can retry via refresh button if needed } finally { setLoading(false); } - }, [listTransactions]); + }, [listTransactions, txLoadFailed]); useEffect(() => { if (isConnected) { loadWalletInfo(); + // Reset transaction loading flags when wallet connects + setTxLoadAttempted(false); + setTxLoadFailed(false); + setTransactions([]); } }, [isConnected, loadWalletInfo]); - // Load transactions when wallet info is available + // Load transactions when wallet info is available (only once) useEffect(() => { - if (walletInfo?.methods.includes("list_transactions")) { + if ( + walletInfo?.methods.includes("list_transactions") && + !txLoadAttempted && + !loading + ) { loadInitialTransactions(); } - }, [walletInfo, loadInitialTransactions]); + }, [walletInfo, txLoadAttempted, loading, loadInitialTransactions]); useEffect(() => { if (!generatedPaymentHash || !receiveDialogOpen) return; @@ -288,6 +305,9 @@ export default function WalletViewer() { toast.success("Payment received!"); setReceiveDialogOpen(false); resetReceiveDialog(); + // Reload transactions - reset flags to allow reload + setTxLoadAttempted(false); + setTxLoadFailed(false); loadInitialTransactions(); } } catch (error) { @@ -491,7 +511,9 @@ export default function WalletViewer() { toast.success("Payment sent successfully"); resetSendDialog(); setSendDialogOpen(false); - // Reload transactions + // Reload transactions - reset flags to allow reload + setTxLoadAttempted(false); + setTxLoadFailed(false); loadInitialTransactions(); } catch (error) { console.error("Payment failed:", error); @@ -823,6 +845,24 @@ export default function WalletViewer() {
+ ) : txLoadFailed ? ( +
+

+ Failed to load transaction history +

+ +
) : transactionsWithMarkers.length === 0 ? (