From b20359634e2d12b0b8d8e0642d2f2abe43da4337 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Jan 2026 17:41:34 +0000 Subject: [PATCH] fix: resolve React error #185 by removing callbacks from useEffect dependencies React error #185 occurs when state updates happen during render due to unstable function references in useEffect dependencies. The root cause was that functions from useWallet (getInfo, listTransactions) aren't memoized, so they create new references on every render, causing the useEffects that depend on them to run repeatedly. Changes: - Removed loadWalletInfo and loadInitialTransactions callbacks - Use refs (walletInfoLoadedRef, lastConnectionStateRef) to track state - Call async functions directly in useEffect without depending on them - Created reloadTransactions() helper that just resets flags - Simplified all reload logic to use the helper This ensures: - No circular dependencies in useEffect - Functions only run once when conditions are met - No state updates during render - Clean, predictable loading behavior --- src/components/WalletViewer.tsx | 125 ++++++++++++++++---------------- 1 file changed, 64 insertions(+), 61 deletions(-) diff --git a/src/components/WalletViewer.tsx b/src/components/WalletViewer.tsx index 9e1dfd2..0628ade 100644 --- a/src/components/WalletViewer.tsx +++ b/src/components/WalletViewer.tsx @@ -5,7 +5,7 @@ * Layout: Header → Big centered balance → Send/Receive buttons → Transaction list */ -import { useState, useEffect, useCallback, useMemo } from "react"; +import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { toast } from "sonner"; import { Wallet, @@ -209,6 +209,10 @@ export default function WalletViewer() { const [txLoadAttempted, setTxLoadAttempted] = useState(false); const [txLoadFailed, setTxLoadFailed] = useState(false); + // Use refs to track loading attempts without causing re-renders + const walletInfoLoadedRef = useRef(false); + const lastConnectionStateRef = useRef(isConnected); + // Send dialog state const [sendDialogOpen, setSendDialogOpen] = useState(false); const [sendInvoice, setSendInvoice] = useState(""); @@ -235,48 +239,34 @@ export default function WalletViewer() { useState(null); const [detailDialogOpen, setDetailDialogOpen] = useState(false); - // Load wallet info on mount - const loadWalletInfo = useCallback(async () => { - try { - const info = await getInfo(); - setWalletInfo(info); - } catch (error) { - console.error("Failed to load wallet info:", error); - toast.error("Failed to load wallet info"); - } - }, [getInfo]); - - const loadInitialTransactions = useCallback(async () => { - setLoading(true); - setTxLoadAttempted(true); - try { - const result = await listTransactions({ - limit: BATCH_SIZE, - offset: 0, - }); - const txs = result.transactions || []; - setTransactions(txs); - setHasMore(txs.length === BATCH_SIZE); - setTxLoadFailed(false); - } catch (error) { - console.error("Failed to load transactions:", error); - setTxLoadFailed(true); - // Don't show toast on initial load - just fail silently - // User can retry via refresh button if needed - } finally { - setLoading(false); - } - }, [listTransactions]); - + // Load wallet info when connected useEffect(() => { - if (isConnected) { - loadWalletInfo(); - // Reset transaction loading flags when wallet connects - setTxLoadAttempted(false); - setTxLoadFailed(false); - setTransactions([]); + // Detect connection state changes + if (isConnected !== lastConnectionStateRef.current) { + lastConnectionStateRef.current = isConnected; + walletInfoLoadedRef.current = false; + + if (isConnected) { + // Reset transaction loading flags when wallet connects + setTxLoadAttempted(false); + setTxLoadFailed(false); + setTransactions([]); + setWalletInfo(null); + } } - }, [isConnected, loadWalletInfo]); + + // Load wallet info if connected and not yet loaded + if (isConnected && !walletInfoLoadedRef.current) { + walletInfoLoadedRef.current = true; + getInfo() + .then((info) => setWalletInfo(info)) + .catch((error) => { + console.error("Failed to load wallet info:", error); + toast.error("Failed to load wallet info"); + walletInfoLoadedRef.current = false; // Allow retry + }); + } + }, [isConnected, getInfo]); // Load transactions when wallet info is available (only once) useEffect(() => { @@ -285,9 +275,34 @@ export default function WalletViewer() { !txLoadAttempted && !loading ) { - loadInitialTransactions(); + setLoading(true); + setTxLoadAttempted(true); + listTransactions({ + limit: BATCH_SIZE, + offset: 0, + }) + .then((result) => { + const txs = result.transactions || []; + setTransactions(txs); + setHasMore(txs.length === BATCH_SIZE); + setTxLoadFailed(false); + }) + .catch((error) => { + console.error("Failed to load transactions:", error); + setTxLoadFailed(true); + }) + .finally(() => { + setLoading(false); + }); } - }, [walletInfo, txLoadAttempted, loading, loadInitialTransactions]); + }, [walletInfo, txLoadAttempted, loading, listTransactions]); + + // Helper to reload transactions (resets flags to trigger reload) + const reloadTransactions = useCallback(() => { + setTxLoadAttempted(false); + setTxLoadFailed(false); + }, []); + useEffect(() => { if (!generatedPaymentHash || !receiveDialogOpen) return; @@ -302,10 +317,8 @@ export default function WalletViewer() { toast.success("Payment received!"); setReceiveDialogOpen(false); resetReceiveDialog(); - // Reload transactions - reset flags to allow reload - setTxLoadAttempted(false); - setTxLoadFailed(false); - loadInitialTransactions(); + // Reload transactions + reloadTransactions(); } } catch (error) { // Ignore errors, will retry @@ -321,7 +334,7 @@ export default function WalletViewer() { receiveDialogOpen, walletInfo, lookupInvoice, - loadInitialTransactions, + reloadTransactions, ]); const loadMoreTransactions = useCallback(async () => { @@ -508,10 +521,8 @@ export default function WalletViewer() { toast.success("Payment sent successfully"); resetSendDialog(); setSendDialogOpen(false); - // Reload transactions - reset flags to allow reload - setTxLoadAttempted(false); - setTxLoadFailed(false); - loadInitialTransactions(); + // Reload transactions + reloadTransactions(); } catch (error) { console.error("Payment failed:", error); toast.error(error instanceof Error ? error.message : "Payment failed"); @@ -847,15 +858,7 @@ export default function WalletViewer() {

Failed to load transaction history

-