mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-16 01:33:03 +02:00
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
This commit is contained in:
@@ -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<Transaction | null>(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() {
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
Failed to load transaction history
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setTxLoadFailed(false);
|
||||
setTxLoadAttempted(false);
|
||||
loadInitialTransactions();
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" size="sm" onClick={reloadTransactions}>
|
||||
<RefreshCw className="mr-2 size-4" />
|
||||
Retry
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user