Skip to content

feat: add onConnect handler to <ConnectWallet /> #1529

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 50 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5e172e7
docs - add missing prop to doc
Oct 24, 2024
8087864
docs
Oct 24, 2024
1ba10f2
revert
Oct 24, 2024
482acb2
save
Oct 24, 2024
f6622fb
fixes
Oct 24, 2024
7ddd0fe
Merge branch 'dms/playground-fixes' into dms/wallet-proto-1
Oct 24, 2024
6ee2629
remove
Oct 24, 2024
e84bff3
remove arb class name
Oct 24, 2024
a3f3cf2
Merge branch 'dms/playground-fixes' into dms/wallet-proto-1
Oct 24, 2024
0dddd5e
prototype
Oct 24, 2024
f6514e2
hm
Oct 25, 2024
1deb207
save
Oct 25, 2024
8830380
prototyping
Oct 28, 2024
87dc933
proto
Oct 28, 2024
db75d31
Merge branch 'main' into dms/wallet-proto-1
Oct 28, 2024
07fc53d
clean
Oct 28, 2024
2c7fbea
cleaning
Oct 28, 2024
ba687b1
cleaning deps
Oct 28, 2024
1d01215
fix
Oct 28, 2024
78ca04d
testing
Oct 28, 2024
84c221e
add tests
Oct 28, 2024
a1c6eca
remove console.log
Oct 28, 2024
a7eb783
clean up comments
Oct 28, 2024
169682c
format
Oct 28, 2024
a44e5a9
fixes
Oct 29, 2024
be2ebba
pnpm
Oct 29, 2024
140ac2d
remove rainbow
Oct 29, 2024
3c4cc2c
make more robust
Oct 29, 2024
3549d07
rewrite tests
Oct 29, 2024
11497f4
remove rainbow
Oct 29, 2024
05b585b
follow conventions for useState and useEffect
Oct 29, 2024
d0720f6
reverts
Oct 29, 2024
69227f8
revert
Oct 29, 2024
c1890e8
package reset
Oct 29, 2024
b0a4c98
format
Oct 29, 2024
af8c06d
remove pnpm
Oct 29, 2024
492d0ea
clean
Oct 29, 2024
c45afcb
format
Oct 29, 2024
cfadac0
reset bun lock
Oct 30, 2024
bdb8b0f
reset
Oct 30, 2024
06dd7b4
reset
Oct 30, 2024
3686242
reset yarn
Oct 30, 2024
330831a
standardize comments
Oct 30, 2024
1f87f10
test
Oct 30, 2024
6440e1e
reset
Oct 30, 2024
3f073a3
cleanup
Oct 30, 2024
209e63a
changeset
Oct 30, 2024
9969089
update
Oct 30, 2024
8d7cdac
typos
Oct 30, 2024
2b84a2e
chore:rename to onConnect
Nov 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"react-dom": "^18"
},
"dependencies": {
"@rainbow-me/rainbowkit": "^2.1.3",
"@rainbow-me/rainbowkit": "^2.2.0",
"@tanstack/react-query": "^5",
"clsx": "^2.1.1",
"graphql": "^14 || ^15 || ^16",
Expand Down
Binary file removed playground/nextjs-app-router/bun.lockb
Binary file not shown.
23 changes: 21 additions & 2 deletions playground/nextjs-app-router/components/demo/Wallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,37 @@ import {
WalletDropdownFundLink,
WalletDropdownLink,
} from '@coinbase/onchainkit/wallet';
import { useAccount } from 'wagmi';
import { useAccount, useSignMessage } from 'wagmi';
import { createSiweMessage } from 'viem/siwe';

import '@rainbow-me/rainbowkit/styles.css';

const message = createSiweMessage({
address: '0xae9eCa1Fa2F786E16D35F5B8C5df3Ac484c490FF',
chainId: 1,
domain: 'example.com',
nonce: 'foobarbaz',
uri: 'https://example.com/path',
version: '1',
});

function WalletComponent() {
const { address } = useAccount();
const { signMessage } = useSignMessage();

return (
<div className="flex justify-end">
<Wallet>
<ConnectWallet text="Connect Wallet">
<ConnectWallet
text="Connect Wallet"
onInitialConnect={() => {
signMessage({ message });
}}
>
<Avatar address={address} className="h-6 w-6" />
<Name />
</ConnectWallet>

<WalletDropdown>
<Identity className="px-4 pt-3 pb-2" hasCopyAddressOnClick={true}>
<Avatar />
Expand Down
2 changes: 1 addition & 1 deletion playground/nextjs-app-router/onchainkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"react-dom": "^18"
},
"dependencies": {
"@rainbow-me/rainbowkit": "^2.1.3",
"@rainbow-me/rainbowkit": "^2.2.0",
"@tanstack/react-query": "^5",
"clsx": "^2.1.1",
"graphql": "^14 || ^15 || ^16",
Expand Down
2 changes: 1 addition & 1 deletion playground/nextjs-app-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"react-dom": "^18",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"viem": "^2.17.4",
"viem": "^2.21.35",
"wagmi": "^2.11.0"
},
"devDependencies": {
Expand Down
11,347 changes: 11,347 additions & 0 deletions playground/nextjs-app-router/pnpm-lock.yaml

Large diffs are not rendered by default.

120 changes: 111 additions & 9 deletions src/wallet/components/ConnectWallet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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' }],
Expand All @@ -98,9 +98,14 @@ describe('ConnectWallet', () => {
render(<ConnectWallet text="Connect Wallet" />);
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', () => {
Expand Down Expand Up @@ -162,6 +167,71 @@ describe('ConnectWallet', () => {
expect(screen.queryByText('Not Render')).not.toBeInTheDocument();
});

it('should call onInitialConnect callback when connect button is clicked', async () => {
const mockUseAccount = vi.mocked(useAccount);
const connectMock = vi.fn();
const onInitialConnectMock = vi.fn();

// Initial state: disconnected
mockUseAccount.mockReturnValue({
address: undefined,
status: 'disconnected',
});

vi.mocked(useConnect).mockReturnValue({
connectors: [{ id: 'mockConnector' }],
connect: connectMock,
status: 'idle',
});

render(
<ConnectWallet
text="Connect Wallet"
onInitialConnect={onInitialConnectMock}
/>,
);

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(
<ConnectWallet
text="Connect Wallet"
onInitialConnect={onInitialConnectMock}
/>,
);

expect(onInitialConnectMock).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 onInitialConnectMock = vi.fn();
render(
<ConnectWallet
text="Connect Wallet"
onInitialConnect={onInitialConnectMock}
/>,
);

expect(onInitialConnectMock).toHaveBeenCalledTimes(0);
});

describe('withWalletAggregator', () => {
beforeEach(() => {
vi.mocked(useAccount).mockReturnValue({
Expand All @@ -175,7 +245,7 @@ describe('ConnectWallet', () => {
});
});

it('should render ConnectButtonRainboKit when withWalletAggregator is true', () => {
it('should render ConnectButtonRainbowKit when withWalletAggregator is true', () => {
render(
<ConnectWallet text="Connect Wallet" withWalletAggregator={true} />,
);
Expand All @@ -198,12 +268,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(),
Expand All @@ -215,5 +290,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 onInitialConnectMock = vi.fn();
render(
<ConnectWallet
text="Connect Wallet"
onInitialConnect={onInitialConnectMock}
withWalletAggregator={true}
/>,
);
const button = screen.getByTestId('ockConnectButton');

mockUseAccount.mockReturnValue({
address: '0x123',
status: 'connected',
});

fireEvent.click(button);

expect(onInitialConnectMock).toHaveBeenCalledTimes(1);
});
});
});
29 changes: 27 additions & 2 deletions src/wallet/components/ConnectWallet.tsx
Original file line number Diff line number Diff line change
@@ -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 React from 'react';
import { useAccount, useConnect } from 'wagmi';
import { IdentityProvider } from '../../identity/components/IdentityProvider';
import { Spinner } from '../../internal/components/Spinner';
Expand All @@ -24,12 +25,16 @@ export function ConnectWallet({
// but for now we will keep it for backward compatibility.
text = 'Connect Wallet',
withWalletAggregator = false,
onInitialConnect,
}: ConnectWalletReact) {
// Core Hooks
const { isOpen, setIsOpen } = useWalletContext();
const { address: accountAddress, status } = useAccount();
const { connectors, connect, status: connectStatus } = useConnect();

// State
const [hasClickedConnect, setHasClickedConnect] = React.useState(false);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we'd be able to avoid this piece of state, but there's no other way to detect whether status === 'connected' is true because:

  1. the user has just signed in OR
  2. the user has refreshed the page


// Get connectWalletText from children when present,
// this is used to customize the connect wallet button text
const { connectWalletText } = useMemo(() => {
Expand Down Expand Up @@ -58,6 +63,14 @@ export function ConnectWallet({
setIsOpen(!isOpen);
}, [isOpen, setIsOpen]);

// Effects
React.useEffect(() => {
if (hasClickedConnect && status === 'connected' && onInitialConnect) {
onInitialConnect();
setHasClickedConnect(false);
}
}, [status, hasClickedConnect, onInitialConnect]);

if (status === 'disconnected') {
if (withWalletAggregator) {
return (
Expand All @@ -67,7 +80,10 @@ export function ConnectWallet({
<ConnectButton
className={className}
connectWalletText={connectWalletText}
onClick={() => openConnectModal()}
onClick={() => {
openConnectModal();
setHasClickedConnect(true);
}}
text={text}
/>
</div>
Expand All @@ -80,7 +96,16 @@ export function ConnectWallet({
<ConnectButton
className={className}
connectWalletText={connectWalletText}
onClick={() => connect({ connector })}
onClick={() => {
connect(
{ connector },
{
onSuccess: () => {
onInitialConnect?.();
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Safer, more reliable way of triggering the callback. We have to use the useEffect and state for RainbowKit support

},
);
}}
text={text}
/>
</div>
Expand Down
6 changes: 6 additions & 0 deletions src/wallet/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type ConnectButtonReact = {
className?: string; // Optional className override for button element
connectWalletText: ReactNode | null; // Optional text override for button
onClick: () => void; // Function to call when the button is clicked
onConnect?: () => void; // Optional callback function to execute when the wallet is connected
text: string; // Optional text override for button
};

Expand All @@ -20,6 +21,11 @@ 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
/**
* Optional callback function to execute when the wallet is connected.
* This can be used to perform additional actions after the wallet is connected.
*/
onInitialConnect?: () => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let's keep this comment inline with onInitialConnect, similar to the other comments in this file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, updated.

BTW the reason for using multiline comments is that the comment then shows up in Intellisense. I think this helps improve DX (internally and externally) but I understand that we already have a convention around // style comments.

CleanShot 2024-10-30 at 08 09 03@2x

};

/**
Expand Down
Loading
Loading