From d4724e92a8f264d39a5c15d99cbd89169d302977 Mon Sep 17 00:00:00 2001
From: Daniel Schlabach <31226559+dschlabach@users.noreply.github.com>
Date: Fri, 1 Nov 2024 09:59:22 -0400
Subject: [PATCH] feat: add `onConnect` handler to `` (#1529)
Co-authored-by: dschlabach
---
.changeset/good-beans-invent.md | 5 +
src/wallet/components/ConnectWallet.test.tsx | 105 +++++++++++++++++--
src/wallet/components/ConnectWallet.tsx | 29 ++++-
src/wallet/types.ts | 1 +
4 files changed, 129 insertions(+), 11 deletions(-)
create mode 100644 .changeset/good-beans-invent.md
diff --git a/.changeset/good-beans-invent.md b/.changeset/good-beans-invent.md
new file mode 100644
index 0000000000..646d4fe64e
--- /dev/null
+++ b/.changeset/good-beans-invent.md
@@ -0,0 +1,5 @@
+---
+'@coinbase/onchainkit': minor
+---
+
+feat: add `onConnect` handler to ``. By @dschlabach #1529
diff --git a/src/wallet/components/ConnectWallet.test.tsx b/src/wallet/components/ConnectWallet.test.tsx
index 1df58c97f9..ab0f1e869f 100644
--- a/src/wallet/components/ConnectWallet.test.tsx
+++ b/src/wallet/components/ConnectWallet.test.tsx
@@ -88,7 +88,7 @@ describe('ConnectWallet', () => {
expect(connectedText).toBeInTheDocument();
});
- it('should calls connect function when connect button is clicked', () => {
+ it('should call connect function when connect button is clicked', () => {
const connectMock = vi.fn();
vi.mocked(useConnect).mockReturnValue({
connectors: [{ id: 'mockConnector' }],
@@ -98,9 +98,14 @@ describe('ConnectWallet', () => {
render();
const button = screen.getByTestId('ockConnectButton');
fireEvent.click(button);
- expect(connectMock).toHaveBeenCalledWith({
- connector: { id: 'mockConnector' },
- });
+ expect(connectMock).toHaveBeenCalledWith(
+ {
+ connector: { id: 'mockConnector' },
+ },
+ {
+ onSuccess: expect.any(Function),
+ },
+ );
});
it('should toggle wallet modal on button click when connected', () => {
@@ -162,6 +167,56 @@ describe('ConnectWallet', () => {
expect(screen.queryByText('Not Render')).not.toBeInTheDocument();
});
+ it('should call onConnect callback when connect button is clicked', async () => {
+ const mockUseAccount = vi.mocked(useAccount);
+ const connectMock = vi.fn();
+ const onConnectMock = vi.fn();
+
+ // Initial state: disconnected
+ mockUseAccount.mockReturnValue({
+ address: undefined,
+ status: 'disconnected',
+ });
+
+ vi.mocked(useConnect).mockReturnValue({
+ connectors: [{ id: 'mockConnector' }],
+ connect: connectMock,
+ status: 'idle',
+ });
+
+ render();
+
+ const button = screen.getByTestId('ockConnectButton');
+ fireEvent.click(button);
+
+ // Simulate successful connection
+ connectMock.mock.calls[0][1].onSuccess();
+
+ // Update account status to connected
+ mockUseAccount.mockReturnValue({
+ address: '0x123',
+ status: 'connected',
+ });
+
+ // Force a re-render to trigger the useEffect
+ render();
+
+ expect(onConnectMock).toHaveBeenCalledTimes(1);
+ });
+
+ it('should not call onConnect callback when component is first mounted', () => {
+ const mockUseAccount = vi.mocked(useAccount);
+ mockUseAccount.mockReturnValue({
+ address: '0x123',
+ status: 'connected',
+ });
+
+ const onConnectMock = vi.fn();
+ render();
+
+ expect(onConnectMock).toHaveBeenCalledTimes(0);
+ });
+
describe('withWalletAggregator', () => {
beforeEach(() => {
vi.mocked(useAccount).mockReturnValue({
@@ -175,7 +230,7 @@ describe('ConnectWallet', () => {
});
});
- it('should render ConnectButtonRainboKit when withWalletAggregator is true', () => {
+ it('should render ConnectButtonRainbowKit when withWalletAggregator is true', () => {
render(
,
);
@@ -198,12 +253,17 @@ describe('ConnectWallet', () => {
);
const connectButton = screen.getByTestId('ockConnectButton');
fireEvent.click(connectButton);
- expect(connectMock).toHaveBeenCalledWith({
- connector: { id: 'mockConnector' },
- });
+ expect(connectMock).toHaveBeenCalledWith(
+ {
+ connector: { id: 'mockConnector' },
+ },
+ {
+ onSuccess: expect.any(Function),
+ },
+ );
});
- it('should calls openConnectModal function when connect button is clicked', () => {
+ it('should call openConnectModal function when connect button is clicked', () => {
vi.mocked(useWalletContext).mockReturnValue({
isOpen: false,
setIsOpen: vi.fn(),
@@ -215,5 +275,32 @@ describe('ConnectWallet', () => {
fireEvent.click(button);
expect(openConnectModalMock).toHaveBeenCalled();
});
+
+ it('should call onConnect callback when connect button is clicked', () => {
+ const mockUseAccount = vi.mocked(useAccount);
+ mockUseAccount.mockReturnValue({
+ address: undefined,
+ status: 'disconnected',
+ });
+
+ const onConnectMock = vi.fn();
+ render(
+ ,
+ );
+ const button = screen.getByTestId('ockConnectButton');
+
+ mockUseAccount.mockReturnValue({
+ address: '0x123',
+ status: 'connected',
+ });
+
+ fireEvent.click(button);
+
+ expect(onConnectMock).toHaveBeenCalledTimes(1);
+ });
});
});
diff --git a/src/wallet/components/ConnectWallet.tsx b/src/wallet/components/ConnectWallet.tsx
index 17dc81f8d8..05ff8bb664 100644
--- a/src/wallet/components/ConnectWallet.tsx
+++ b/src/wallet/components/ConnectWallet.tsx
@@ -1,6 +1,7 @@
import { ConnectButton as ConnectButtonRainbowKit } from '@rainbow-me/rainbowkit';
import { Children, isValidElement, useCallback, useMemo } from 'react';
import type { ReactNode } from 'react';
+import { useEffect, useState } from 'react';
import { useAccount, useConnect } from 'wagmi';
import { IdentityProvider } from '../../identity/components/IdentityProvider';
import { Spinner } from '../../internal/components/Spinner';
@@ -24,12 +25,16 @@ export function ConnectWallet({
// but for now we will keep it for backward compatibility.
text = 'Connect Wallet',
withWalletAggregator = false,
+ onConnect,
}: ConnectWalletReact) {
// Core Hooks
const { isOpen, setIsOpen } = useWalletContext();
const { address: accountAddress, status } = useAccount();
const { connectors, connect, status: connectStatus } = useConnect();
+ // State
+ const [hasClickedConnect, setHasClickedConnect] = useState(false);
+
// Get connectWalletText from children when present,
// this is used to customize the connect wallet button text
const { connectWalletText } = useMemo(() => {
@@ -58,6 +63,14 @@ export function ConnectWallet({
setIsOpen(!isOpen);
}, [isOpen, setIsOpen]);
+ // Effects
+ useEffect(() => {
+ if (hasClickedConnect && status === 'connected' && onConnect) {
+ onConnect();
+ setHasClickedConnect(false);
+ }
+ }, [status, hasClickedConnect, onConnect]);
+
if (status === 'disconnected') {
if (withWalletAggregator) {
return (
@@ -67,7 +80,10 @@ export function ConnectWallet({
openConnectModal()}
+ onClick={() => {
+ openConnectModal();
+ setHasClickedConnect(true);
+ }}
text={text}
/>
@@ -80,7 +96,16 @@ export function ConnectWallet({
connect({ connector })}
+ onClick={() => {
+ connect(
+ { connector },
+ {
+ onSuccess: () => {
+ onConnect?.();
+ },
+ },
+ );
+ }}
text={text}
/>
diff --git a/src/wallet/types.ts b/src/wallet/types.ts
index f6fbdaf057..3e613d9b2b 100644
--- a/src/wallet/types.ts
+++ b/src/wallet/types.ts
@@ -20,6 +20,7 @@ export type ConnectWalletReact = {
/** @deprecated Prefer `ConnectWalletText component` */
text?: string; // Optional text override for button
withWalletAggregator?: boolean; // Optional flag to enable the wallet aggregator like RainbowKit
+ onConnect?: () => void; // Optional callback function to execute when the wallet is connected.
};
/**