Skip to content

Commit cd3ca75

Browse files
authored
Merge pull request #172 from MeshJS/bug/wallet-connector
Enhance ConnectWallet component with loading state and client-side re…
2 parents 41a43ae + 7ae9b48 commit cd3ca75

File tree

2 files changed

+102
-17
lines changed

2 files changed

+102
-17
lines changed

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

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from "@/components/ui/dropdown-menu";
1111
import { useWallet, useWalletList } from "@meshsdk/react";
1212
import { useSiteStore } from "@/lib/zustand/site";
13-
import { useEffect, useRef } from "react";
13+
import { useEffect, useRef, useState } from "react";
1414
import useUser from "@/hooks/useUser";
1515
import { useUserStore } from "@/lib/zustand/user";
1616
import { getProvider } from "@/utils/get-provider";
@@ -35,21 +35,87 @@ export default function ConnectWallet() {
3535
const fetchingNetworkRef = useRef(false);
3636
const lastNetworkWalletRef = useRef<string | null>(null);
3737
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+
}, []);
3846

3947
async function connectWallet(walletId: string) {
4048
setPastWallet(walletId);
4149
await connect(walletId);
4250
}
4351

52+
// Monitor wallet list loading state and retry if empty
53+
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+
44106
/**
45107
* Try to connect the wallet when the user loads the application, if user had connected before,
46108
* but only if:
47-
* 1. The wallet list has been loaded (wallets.length > 0)
48-
* 2. The pastWallet exists in the available wallets
49-
* 3. We're not already connected
50-
* 4. We're not already attempting to connect
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
51114
*/
52115
useEffect(() => {
116+
// Only run on client side
117+
if (!isMounted) return;
118+
53119
async function handleAutoWalletConnect() {
54120
// Don't attempt if already connected or already connecting
55121
if (connected || connectingRef.current) {
@@ -65,6 +131,7 @@ export default function ConnectWallet() {
65131
// If wallets array is empty, wallets might still be loading
66132
// The effect will re-run when wallets become available
67133
if (wallets.length === 0) {
134+
console.log("Waiting for wallets to load...");
68135
return;
69136
}
70137

@@ -96,7 +163,7 @@ export default function ConnectWallet() {
96163
}
97164

98165
handleAutoWalletConnect();
99-
}, [pastWallet, connected, wallets, connect, setPastWallet]);
166+
}, [pastWallet, connected, wallets, connect, setPastWallet, isMounted]);
100167

101168
useEffect(() => {
102169
async function lookupWalletAssets() {
@@ -226,12 +293,12 @@ export default function ConnectWallet() {
226293
}
227294
}
228295

229-
// Only run if wallet and connected state are available
230-
if (wallet && connected) {
296+
// Only run if wallet and connected state are available, and component is mounted
297+
if (isMounted && wallet && connected) {
231298
handleNetworkChange();
232299
getWalletAssets();
233300
}
234-
}, [connected, wallet, name, setNetwork, setUserAssets, setUserAssetMetadata]);
301+
}, [connected, wallet, name, setNetwork, setUserAssets, setUserAssetMetadata, isMounted]);
235302

236303
return (
237304
<DropdownMenu>
@@ -246,13 +313,25 @@ export default function ConnectWallet() {
246313
<DropdownMenuContent align="end">
247314
<DropdownMenuLabel>Select Wallet</DropdownMenuLabel>
248315
<DropdownMenuSeparator />
249-
{wallets.map((wallet, i) => {
250-
return (
251-
<DropdownMenuItem key={i} onClick={() => connectWallet(wallet.id)}>
252-
{wallet.name}
253-
</DropdownMenuItem>
254-
);
255-
})}
316+
{walletsLoading && wallets.length === 0 ? (
317+
<DropdownMenuItem disabled>
318+
<span className="text-muted-foreground">Loading wallets...</span>
319+
</DropdownMenuItem>
320+
) : wallets.length === 0 ? (
321+
<DropdownMenuItem disabled>
322+
<span className="text-muted-foreground">
323+
No wallets available. Please install a Cardano wallet extension.
324+
</span>
325+
</DropdownMenuItem>
326+
) : (
327+
wallets.map((wallet, i) => {
328+
return (
329+
<DropdownMenuItem key={i} onClick={() => connectWallet(wallet.id)}>
330+
{wallet.name}
331+
</DropdownMenuItem>
332+
);
333+
})
334+
)}
256335
</DropdownMenuContent>
257336
</DropdownMenu>
258337
);

src/components/common/overall-layout/layout.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,17 @@ import {
2424
import LogoutWrapper from "@/components/common/overall-layout/mobile-wrappers/logout-wrapper";
2525
import { PageHomepage } from "@/components/pages/homepage";
2626
import Logo from "@/components/common/overall-layout/logo";
27-
import ConnectWallet from "@/components/common/cardano-objects/connect-wallet";
27+
import dynamic from "next/dynamic";
2828
import Loading from "@/components/common/overall-layout/loading";
2929
import { MobileNavigation } from "@/components/ui/mobile-navigation";
3030
import { MobileActionsMenu } from "@/components/ui/mobile-actions-menu";
3131

32+
// Dynamically import ConnectWallet with SSR disabled to avoid production SSR issues
33+
const ConnectWallet = dynamic(
34+
() => import("@/components/common/cardano-objects/connect-wallet"),
35+
{ ssr: false }
36+
);
37+
3238
// Enhanced error boundary component for wallet errors
3339
class WalletErrorBoundary extends Component<
3440
{ children: ReactNode; fallback: ReactNode },

0 commit comments

Comments
 (0)