Skip to content

Commit 0b6e19c

Browse files
committed
Refactor ConnectWallet and RootLayout components for improved state management and user experience
- Integrated new hooks for network and asset management in ConnectWallet, streamlining wallet connection and asset processing. - Removed unnecessary state management and retry logic, simplifying the component's structure. - Enhanced RootLayout to synchronize user address from the wallet, ensuring accurate user data handling during wallet initialization. - Improved error handling and logging for wallet operations, providing clearer feedback during user interactions.
1 parent 7ae9b48 commit 0b6e19c

File tree

2 files changed

+69
-344
lines changed

2 files changed

+69
-344
lines changed

src/components/common/cardano-objects/connect-wallet.tsx

Lines changed: 41 additions & 254 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ import {
88
DropdownMenuSeparator,
99
DropdownMenuTrigger,
1010
} from "@/components/ui/dropdown-menu";
11-
import { useWallet, useWalletList } from "@meshsdk/react";
11+
import { useWallet, useWalletList, useNetwork, useAssets } from "@meshsdk/react";
1212
import { useSiteStore } from "@/lib/zustand/site";
13-
import { useEffect, useRef, useState } from "react";
13+
import { useEffect } from "react";
1414
import useUser from "@/hooks/useUser";
1515
import { useUserStore } from "@/lib/zustand/user";
1616
import { getProvider } from "@/utils/get-provider";
17-
import { Asset } from "@meshsdk/core";
1817

1918
export default function ConnectWallet() {
2019
const setNetwork = useSiteStore((state) => state.setNetwork);
@@ -28,277 +27,69 @@ export default function ConnectWallet() {
2827

2928
const wallets = useWalletList();
3029
const { connect, connected, wallet, name } = useWallet();
30+
const networkId = useNetwork();
31+
const assets = useAssets();
3132
const network = useSiteStore((state) => state.network);
32-
const connectingRef = useRef(false);
33-
const fetchingAssetsRef = useRef(false);
34-
const lastWalletIdRef = useRef<string | null>(null);
35-
const fetchingNetworkRef = useRef(false);
36-
const lastNetworkWalletRef = useRef<string | null>(null);
37-
const userAssets = useUserStore((state) => state.userAssets);
38-
const [walletsLoading, setWalletsLoading] = useState(true);
39-
const [isMounted, setIsMounted] = useState(false);
40-
const walletsRetryTimeoutRef = useRef<NodeJS.Timeout | null>(null);
41-
42-
// Ensure component only runs on client side (important for SSR/production)
43-
useEffect(() => {
44-
setIsMounted(true);
45-
}, []);
4633

4734
async function connectWallet(walletId: string) {
4835
setPastWallet(walletId);
4936
await connect(walletId);
5037
}
5138

52-
// Monitor wallet list loading state and retry if empty
39+
// Auto-connect if user had connected before
5340
useEffect(() => {
54-
// Only run on client side
55-
if (!isMounted) return;
56-
57-
if (wallets.length > 0) {
58-
setWalletsLoading(false);
59-
// Clear any pending retry timeout
60-
if (walletsRetryTimeoutRef.current) {
61-
clearTimeout(walletsRetryTimeoutRef.current);
62-
walletsRetryTimeoutRef.current = null;
63-
}
64-
} else if (!connected) {
65-
// Only show loading state if not connected (wallets might load after connection)
66-
// If wallets are empty, wait a bit and check again
67-
// This handles cases where wallet extensions load asynchronously
68-
if (walletsRetryTimeoutRef.current === null) {
69-
setWalletsLoading(true);
70-
let retryCount = 0;
71-
const maxRetries = 10; // Try for up to 10 seconds in production (longer timeout)
72-
73-
const checkWallets = () => {
74-
retryCount++;
75-
// Re-check wallets array length (it might have updated)
76-
if (wallets.length > 0) {
77-
setWalletsLoading(false);
78-
walletsRetryTimeoutRef.current = null;
79-
return;
80-
}
81-
82-
if (retryCount < maxRetries) {
83-
walletsRetryTimeoutRef.current = setTimeout(checkWallets, 1000); // Check every second
84-
} else {
85-
console.warn("Wallet list still empty after retries, wallets may not be available");
86-
setWalletsLoading(false); // Stop showing loading state
87-
walletsRetryTimeoutRef.current = null;
88-
}
89-
};
90-
91-
walletsRetryTimeoutRef.current = setTimeout(checkWallets, 1000);
92-
}
93-
} else {
94-
// If connected but no wallets, they're probably not needed
95-
setWalletsLoading(false);
96-
}
97-
98-
return () => {
99-
if (walletsRetryTimeoutRef.current) {
100-
clearTimeout(walletsRetryTimeoutRef.current);
101-
walletsRetryTimeoutRef.current = null;
102-
}
103-
};
104-
}, [wallets, connected, isMounted]);
105-
106-
/**
107-
* Try to connect the wallet when the user loads the application, if user had connected before,
108-
* but only if:
109-
* 1. Component is mounted (client-side only)
110-
* 2. The wallet list has been loaded (wallets.length > 0)
111-
* 3. The pastWallet exists in the available wallets
112-
* 4. We're not already connected
113-
* 5. We're not already attempting to connect
114-
*/
115-
useEffect(() => {
116-
// Only run on client side
117-
if (!isMounted) return;
118-
119-
async function handleAutoWalletConnect() {
120-
// Don't attempt if already connected or already connecting
121-
if (connected || connectingRef.current) {
122-
return;
123-
}
124-
125-
// Don't attempt if no pastWallet is stored
126-
if (!pastWallet) {
127-
return;
128-
}
129-
130-
// Wait for wallet list to be available
131-
// If wallets array is empty, wallets might still be loading
132-
// The effect will re-run when wallets become available
133-
if (wallets.length === 0) {
134-
console.log("Waiting for wallets to load...");
135-
return;
136-
}
137-
138-
// Check if the pastWallet exists in the available wallets
41+
if (pastWallet && !connected && wallets.length > 0) {
13942
const walletExists = wallets.some((w) => w.id === pastWallet);
140-
if (!walletExists) {
141-
console.warn(
142-
`Stored wallet "${pastWallet}" not found in available wallets. Clearing stored wallet.`,
143-
);
43+
if (walletExists) {
44+
connect(pastWallet).catch(() => {
45+
setPastWallet(undefined);
46+
});
47+
} else {
14448
setPastWallet(undefined);
145-
return;
146-
}
147-
148-
// Attempt to connect
149-
connectingRef.current = true;
150-
try {
151-
console.log(`Attempting to auto-connect wallet: ${pastWallet}`);
152-
await connect(pastWallet);
153-
console.log(`Successfully auto-connected wallet: ${pastWallet}`);
154-
} catch (e) {
155-
console.error(
156-
`Failed to auto-connect wallet "${pastWallet}":`,
157-
e instanceof Error ? e.message : e,
158-
);
159-
setPastWallet(undefined);
160-
} finally {
161-
connectingRef.current = false;
16249
}
16350
}
51+
}, [pastWallet, connected, wallets, connect, setPastWallet]);
16452

165-
handleAutoWalletConnect();
166-
}, [pastWallet, connected, wallets, connect, setPastWallet, isMounted]);
167-
53+
// Sync network from hook to store
16854
useEffect(() => {
169-
async function lookupWalletAssets() {
170-
if (!wallet) return;
171-
172-
// Prevent multiple simultaneous calls
173-
if (fetchingAssetsRef.current) {
174-
console.log("Assets fetch already in progress, skipping...");
175-
return;
176-
}
177-
178-
// Use wallet name as identifier (doesn't require API call)
179-
const walletId = name || "unknown";
180-
181-
// Skip if we've already fetched for this wallet and have assets
182-
if (lastWalletIdRef.current === walletId && userAssets.length > 0) {
183-
console.log("Assets already loaded for this wallet, skipping fetch");
184-
return;
185-
}
55+
if (networkId !== undefined && networkId !== null) {
56+
setNetwork(networkId);
57+
}
58+
}, [networkId, setNetwork]);
18659

187-
fetchingAssetsRef.current = true;
188-
lastWalletIdRef.current = walletId;
60+
// Process assets and fetch metadata
61+
useEffect(() => {
62+
if (!connected || !assets || assets.length === 0 || networkId === undefined || networkId === null) return;
18963

64+
async function processAssets() {
65+
if (!assets || networkId === undefined || networkId === null) return;
66+
19067
try {
191-
console.log("Fetching wallet balance...");
192-
const assets = await wallet.getBalance();
193-
// Use network from store if available, otherwise fetch it
194-
let networkId = network;
195-
if (!networkId) {
196-
try {
197-
networkId = await wallet.getNetworkId();
198-
setNetwork(networkId);
199-
} catch (networkError) {
200-
console.error("Error getting network ID for provider:", networkError);
201-
// Use default network if we can't get it
202-
networkId = 0; // Mainnet default
203-
}
204-
}
20568
const provider = getProvider(networkId);
206-
const fetchedAssets: Asset[] = [];
207-
if (assets) {
208-
for (const asset of assets) {
209-
fetchedAssets.push({
210-
unit: asset.unit,
211-
quantity: asset.quantity,
212-
});
213-
if (asset.unit === "lovelace") continue;
214-
try {
215-
const assetInfo = await provider.get(`/assets/${asset.unit}`);
216-
setUserAssetMetadata(
69+
setUserAssets(assets);
70+
71+
for (const asset of assets) {
72+
if (asset.unit === "lovelace") continue;
73+
try {
74+
const assetInfo = await provider.get(`/assets/${asset.unit}`);
75+
setUserAssetMetadata(
76+
asset.unit,
77+
assetInfo?.metadata?.name ||
78+
assetInfo?.onchain_metadata?.name ||
21779
asset.unit,
218-
assetInfo?.metadata?.name ||
219-
assetInfo?.onchain_metadata?.name ||
220-
asset.unit,
221-
assetInfo?.metadata?.decimals || 0,
222-
);
223-
} catch (assetError) {
224-
// If asset metadata fetch fails, continue with other assets
225-
console.warn(`Failed to fetch metadata for asset ${asset.unit}:`, assetError);
226-
}
80+
assetInfo?.metadata?.decimals || 0,
81+
);
82+
} catch (error) {
83+
// Continue if asset metadata fetch fails
22784
}
228-
setUserAssets(fetchedAssets);
229-
console.log("Successfully fetched wallet assets");
230-
}
231-
// Reset the fetching flag after successful fetch
232-
fetchingAssetsRef.current = false;
233-
} catch (error) {
234-
console.error("Error looking up wallet assets:", error);
235-
// If it's a rate limit error, don't clear the ref immediately
236-
// to prevent rapid retries - wait 5 seconds before allowing retry
237-
if (error instanceof Error && error.message.includes("too many requests")) {
238-
console.warn("Rate limit hit, will retry after delay");
239-
setTimeout(() => {
240-
fetchingAssetsRef.current = false;
241-
}, 5000);
242-
return;
24385
}
244-
// For other errors, reset immediately so we can retry
245-
fetchingAssetsRef.current = false;
246-
}
247-
}
248-
249-
async function handleNetworkChange() {
250-
if (!connected || !wallet) return;
251-
252-
// Prevent multiple simultaneous network ID fetches
253-
if (fetchingNetworkRef.current) {
254-
console.log("Network ID fetch already in progress, skipping...");
255-
return;
256-
}
257-
258-
// Use wallet name as identifier (doesn't require API call)
259-
const walletId = name || "unknown";
260-
261-
// Skip if we've already fetched network for this wallet
262-
if (lastNetworkWalletRef.current === walletId && network !== undefined) {
263-
console.log("Network ID already fetched for this wallet, skipping");
264-
return;
265-
}
266-
267-
fetchingNetworkRef.current = true;
268-
lastNetworkWalletRef.current = walletId;
269-
270-
try {
271-
console.log("Fetching network ID...");
272-
const networkId = await wallet.getNetworkId();
273-
setNetwork(networkId);
274-
console.log("Successfully fetched network ID:", networkId);
275-
fetchingNetworkRef.current = false;
27686
} catch (error) {
277-
console.error("Error getting network ID:", error);
278-
// If rate limited, wait before retry
279-
if (error instanceof Error && error.message.includes("too many requests")) {
280-
console.warn("Rate limit hit for network ID, will retry after delay");
281-
setTimeout(() => {
282-
fetchingNetworkRef.current = false;
283-
}, 5000);
284-
return;
285-
}
286-
fetchingNetworkRef.current = false;
87+
console.error("Error processing assets:", error);
28788
}
28889
}
28990

290-
async function getWalletAssets() {
291-
if (wallet && connected) {
292-
await lookupWalletAssets();
293-
}
294-
}
295-
296-
// Only run if wallet and connected state are available, and component is mounted
297-
if (isMounted && wallet && connected) {
298-
handleNetworkChange();
299-
getWalletAssets();
300-
}
301-
}, [connected, wallet, name, setNetwork, setUserAssets, setUserAssetMetadata, isMounted]);
91+
processAssets();
92+
}, [assets, connected, networkId, setUserAssets, setUserAssetMetadata]);
30293

30394
return (
30495
<DropdownMenu>
@@ -313,11 +104,7 @@ export default function ConnectWallet() {
313104
<DropdownMenuContent align="end">
314105
<DropdownMenuLabel>Select Wallet</DropdownMenuLabel>
315106
<DropdownMenuSeparator />
316-
{walletsLoading && wallets.length === 0 ? (
317-
<DropdownMenuItem disabled>
318-
<span className="text-muted-foreground">Loading wallets...</span>
319-
</DropdownMenuItem>
320-
) : wallets.length === 0 ? (
107+
{wallets.length === 0 ? (
321108
<DropdownMenuItem disabled>
322109
<span className="text-muted-foreground">
323110
No wallets available. Please install a Cardano wallet extension.

0 commit comments

Comments
 (0)