From eedb049a523eff9f462facdb076cdfddec7c10de Mon Sep 17 00:00:00 2001 From: Daniel Moerner Date: Wed, 23 Sep 2026 13:51:27 -0400 Subject: [PATCH] fix(ui): Stop UserProfile crashing after connecting the last Web3 wallet Connecting the last unconnected Web3 wallet strategy unmounted the whole UserProfile with "Rendered fewer hooks than expected". Once the wallet is verified, AddWeb3WalletActionMenu returns null before calling useReverification, so it calls fewer hooks than on the previous render. With a single enabled Web3 provider, this happened on every successful connect. Call useReverification before the early return. Co-Authored-By: Claude Opus 5.5 --- .changeset/web3-wallet-add-hooks-crash.md | 5 ++++ .../src/components/UserProfile/Web3Form.tsx | 7 +++-- .../__tests__/Web3Section.test.tsx | 26 +++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 .changeset/web3-wallet-add-hooks-crash.md diff --git a/.changeset/web3-wallet-add-hooks-crash.md b/.changeset/web3-wallet-add-hooks-crash.md new file mode 100644 index 00000000000..89750b6198c --- /dev/null +++ b/.changeset/web3-wallet-add-hooks-crash.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +Fix `` crashing with "Rendered fewer hooks than expected" after connecting the last available Web3 wallet. diff --git a/packages/ui/src/components/UserProfile/Web3Form.tsx b/packages/ui/src/components/UserProfile/Web3Form.tsx index 39cb0e54f0a..95b18d82444 100644 --- a/packages/ui/src/components/UserProfile/Web3Form.tsx +++ b/packages/ui/src/components/UserProfile/Web3Form.tsx @@ -25,15 +25,14 @@ export const AddWeb3WalletActionMenu = () => { const unconnectedStrategies = enabledStrategies.filter(strategy => { return !connectedStrategies.includes(strategy) && strategyToDisplayData[strategy]; }); + const createWeb3Wallet = useReverification((identifier: string) => + user?.createWeb3Wallet({ web3Wallet: identifier }), + ); if (unconnectedStrategies.length === 0) { return null; } - const createWeb3Wallet = useReverification((identifier: string) => - user?.createWeb3Wallet({ web3Wallet: identifier }), - ); - // If the user selects `web3_solana_signature` as their strategy, // we need to obtain the wallet name to use when connecting and signing the message during the auth flow // diff --git a/packages/ui/src/components/UserProfile/__tests__/Web3Section.test.tsx b/packages/ui/src/components/UserProfile/__tests__/Web3Section.test.tsx index e972492ea2f..d77f735ca7b 100644 --- a/packages/ui/src/components/UserProfile/__tests__/Web3Section.test.tsx +++ b/packages/ui/src/components/UserProfile/__tests__/Web3Section.test.tsx @@ -178,4 +178,30 @@ describe('Web3Section', () => { }); }); }); + + // Regression: connecting the last unconnected strategy used to crash the whole + // UserProfile with "Rendered fewer hooks than expected", because the action menu + // returned early before calling `useReverification`. + it('keeps rendering after the last enabled wallet strategy gets connected', async () => { + // GIVEN MetaMask is the only enabled web3 strategy and the user has not connected it + const withMetamaskEnabled = createFixtures.config(f => { + f.withWeb3Wallet(); + f.withUser({ email_addresses: ['test@clerk.com'] }); + }); + const { wrapper, fixtures } = await createFixtures(withMetamaskEnabled); + const { getByRole, getByText, queryByRole, rerender } = render(, { wrapper }); + getByRole('button', { name: /Connect wallet/i }); + + // WHEN the user resource updates with a verified MetaMask wallet, as it does after attemptVerification + const { fixtures: connected } = await createFixtures(withMetamaskWallet); + (fixtures.clerk as any).__internal_lastEmittedResources = { + ...(fixtures.clerk as any).__internal_lastEmittedResources, + user: connected.clerk.user, + }; + rerender(); + + // THEN the wallet is listed and the connect menu is gone + getByText(/0x1234...5678/); + expect(queryByRole('button', { name: /Connect wallet/i })).toBeNull(); + }); });