Skip to content
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

Feature: Connection switch #105

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 14 additions & 5 deletions packages/core/react/src/ConnectionProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Connection, ConnectionConfig } from '@solana/web3.js';
import React, { FC, ReactNode, useMemo } from 'react';
import { ConnectionContext } from './useConnection';
import React, { FC, ReactNode, useEffect, useMemo, useReducer } from 'react';
import { ConnectionContext, ConnectionDispatchContext } from './useConnection';

export interface ConnectionProviderProps {
children: ReactNode;
Expand All @@ -13,7 +13,16 @@ export const ConnectionProvider: FC<ConnectionProviderProps> = ({
endpoint,
config = { commitment: 'confirmed' },
}) => {
const connection = useMemo(() => new Connection(endpoint, config), [endpoint, config]);

return <ConnectionContext.Provider value={{ connection }}>{children}</ConnectionContext.Provider>;
const [connection, setConnection] = useReducer(
(_: Connection, newConnection: Connection) => newConnection,
new Connection(endpoint, config)
);
kevinrodriguez-io marked this conversation as resolved.
Show resolved Hide resolved
useEffect(() => {
setConnection(new Connection(endpoint, config));
}, [endpoint, config]);
return (
<ConnectionDispatchContext.Provider value={setConnection}>
<ConnectionContext.Provider value={{ connection }}>{children}</ConnectionContext.Provider>
</ConnectionDispatchContext.Provider>
);
};
6 changes: 6 additions & 0 deletions packages/core/react/src/useConnection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ export interface ConnectionContextState {
}

export const ConnectionContext = createContext<ConnectionContextState>({} as ConnectionContextState);
export const ConnectionDispatchContext = createContext<React.Dispatch<Connection>>({} as React.Dispatch<Connection>);

export function useConnection(): ConnectionContextState {
return useContext(ConnectionContext);
}

export function useSetConnection(): (connection: Connection) => void {
const dispatch = useContext(ConnectionDispatchContext);
return (connection: Connection) => dispatch(connection);
}
Comment on lines +14 to +18
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems overly complicated. Why not just const [connection, setConnection] = useState(() => new Connection(...)) in the provider and then pass setConnection into the context?

208 changes: 114 additions & 94 deletions packages/starter/example/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
WalletModalButton as AntDesignWalletModalButton,
WalletModalProvider as AntDesignWalletModalProvider,
WalletMultiButton as AntDesignWalletMultiButton,
ConnectionModalProvider as AntDesignConnectionModalProvider,
} from '@solana/wallet-adapter-ant-design';
import {
WalletConnectButton as MaterialUIWalletConnectButton,
Expand All @@ -26,107 +27,126 @@ import { useAutoConnect } from '../components/AutoConnectProvider';
import RequestAirdrop from '../components/RequestAirdrop';
import SendTransaction from '../components/SendTransaction';
import SignMessage from '../components/SignMessage';
import { clusterApiUrl } from '@solana/web3.js';
import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';

const Index: NextPage = () => {
const { autoConnect, setAutoConnect } = useAutoConnect();

return (
<MaterialUIWalletDialogProvider>
<AntDesignWalletModalProvider>
<ReactUIWalletModalProvider>
<Table>
<TableHead>
<TableRow>
<TableCell width={240}>Component</TableCell>
<TableCell width={240}>Material UI</TableCell>
<TableCell width={240}>Ant Design</TableCell>
<TableCell width={240}>React UI</TableCell>
<TableCell>Example v{pkg.version}</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Connect Button</TableCell>
<TableCell>
<MaterialUIWalletConnectButton />
</TableCell>
<TableCell>
<AntDesignWalletConnectButton />
</TableCell>
<TableCell>
<ReactUIWalletConnectButton />
</TableCell>
<TableCell></TableCell>
</TableRow>
<TableRow>
<TableCell>Disconnect Button</TableCell>
<TableCell>
<MaterialUIWalletDisconnectButton />
</TableCell>
<TableCell>
<AntDesignWalletDisconnectButton />
</TableCell>
<TableCell>
<ReactUIWalletDisconnectButton />
</TableCell>
<TableCell></TableCell>
</TableRow>
<TableRow>
<TableCell>Dialog/Modal Button</TableCell>
<TableCell>
<MaterialUIWalletDialogButton />
</TableCell>
<TableCell>
<AntDesignWalletModalButton />
</TableCell>
<TableCell>
<ReactUIWalletModalButton />
</TableCell>
<TableCell></TableCell>
</TableRow>
<TableRow>
<TableCell>Multi Button</TableCell>
<TableCell>
<MaterialUIWalletMultiButton />
</TableCell>
<TableCell>
<AntDesignWalletMultiButton />
</TableCell>
<TableCell>
<ReactUIWalletMultiButton />
</TableCell>
<TableCell></TableCell>
</TableRow>
<TableRow>
<TableCell></TableCell>
<TableCell>
<Tooltip title="Only runs if the wallet is ready to connect" placement="left">
<FormControlLabel
control={
<Switch
name="autoConnect"
color="secondary"
checked={autoConnect}
onChange={(event, checked) => setAutoConnect(checked)}
/>
}
label="AutoConnect"
/>
</Tooltip>
</TableCell>
<TableCell>
<RequestAirdrop />
</TableCell>
<TableCell>
<SendTransaction />
</TableCell>
<TableCell>
<SignMessage />
</TableCell>
</TableRow>
</TableBody>
</Table>
</ReactUIWalletModalProvider>
<AntDesignConnectionModalProvider
connections={[
{
endpoint: clusterApiUrl(WalletAdapterNetwork.Mainnet),
name: 'Mainnet',
},
{
endpoint: clusterApiUrl(WalletAdapterNetwork.Devnet),
name: 'Devnet',
},
{
endpoint: clusterApiUrl(WalletAdapterNetwork.Testnet),
name: 'Testnet',
Comment on lines +42 to +51
Copy link
Collaborator

@jordaaash jordaaash Oct 4, 2021

Choose a reason for hiding this comment

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

Names like this should be an enum or some other typesafe structure so that child components are sure to input the correct values. The connections array could be part of the ConnectionProvider since it's not dependent upon UI, and the connection value can be an item in that array.

},
]}
>
<ReactUIWalletModalProvider>
<Table>
<TableHead>
<TableRow>
<TableCell width={240}>Component</TableCell>
<TableCell width={240}>Material UI</TableCell>
<TableCell width={240}>Ant Design</TableCell>
<TableCell width={240}>React UI</TableCell>
<TableCell>Example v{pkg.version}</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Connect Button</TableCell>
<TableCell>
<MaterialUIWalletConnectButton />
</TableCell>
<TableCell>
<AntDesignWalletConnectButton />
</TableCell>
<TableCell>
<ReactUIWalletConnectButton />
</TableCell>
<TableCell></TableCell>
</TableRow>
<TableRow>
<TableCell>Disconnect Button</TableCell>
<TableCell>
<MaterialUIWalletDisconnectButton />
</TableCell>
<TableCell>
<AntDesignWalletDisconnectButton />
</TableCell>
<TableCell>
<ReactUIWalletDisconnectButton />
</TableCell>
<TableCell></TableCell>
</TableRow>
<TableRow>
<TableCell>Dialog/Modal Button</TableCell>
<TableCell>
<MaterialUIWalletDialogButton />
</TableCell>
<TableCell>
<AntDesignWalletModalButton />
</TableCell>
<TableCell>
<ReactUIWalletModalButton />
</TableCell>
<TableCell></TableCell>
</TableRow>
<TableRow>
<TableCell>Multi Button</TableCell>
<TableCell>
<MaterialUIWalletMultiButton />
</TableCell>
<TableCell>
<AntDesignWalletMultiButton />
</TableCell>
<TableCell>
<ReactUIWalletMultiButton />
</TableCell>
<TableCell></TableCell>
</TableRow>
<TableRow>
<TableCell></TableCell>
<TableCell>
<Tooltip title="Only runs if the wallet is ready to connect" placement="left">
<FormControlLabel
control={
<Switch
name="autoConnect"
color="secondary"
checked={autoConnect}
onChange={(event, checked) => setAutoConnect(checked)}
/>
}
label="AutoConnect"
/>
</Tooltip>
</TableCell>
<TableCell>
<RequestAirdrop />
</TableCell>
<TableCell>
<SendTransaction />
</TableCell>
<TableCell>
<SignMessage />
</TableCell>
</TableRow>
</TableBody>
</Table>
</ReactUIWalletModalProvider>
</AntDesignConnectionModalProvider>
</AntDesignWalletModalProvider>
</MaterialUIWalletDialogProvider>
);
Expand Down
19 changes: 19 additions & 0 deletions packages/ui/ant-design/src/ConnectionMenuItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Button, Menu, MenuItemProps } from 'antd';
import React, { FC, MouseEventHandler } from 'react';

interface ConnectionMenuItemProps extends Omit<MenuItemProps, 'onClick'> {
onClick: MouseEventHandler<HTMLButtonElement>;
connection: {
name: string;
};
Comment on lines +6 to +8
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be a named interface provided by the context.

}

export const ConnectionMenuItem: FC<ConnectionMenuItemProps> = ({ onClick, connection, ...props }) => {
return (
<Menu.Item className="wallet-adapter-modal-menu-item" {...props}>
<Button onClick={onClick} type="text" className="wallet-adapter-connection-modal-menu-button" block>
{connection.name}
</Button>
</Menu.Item>
);
};
67 changes: 67 additions & 0 deletions packages/ui/ant-design/src/ConnectionModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useSetConnection } from '@solana/wallet-adapter-react';
import { Menu, Modal, ModalProps } from 'antd';
import React, { FC, MouseEvent, useCallback, useMemo, useState } from 'react';
import { useConnectionModal } from './useConnectionModal';
import { ConnectionMenuItem } from './ConnectionMenuItem';
import { Connection } from '@solana/web3.js';

export interface ConnectionModalProps extends Omit<ModalProps, 'visible'> {
connections: {
name: string;
endpoint: string;
}[];
Comment on lines +9 to +12
Copy link
Collaborator

Choose a reason for hiding this comment

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

Always good to export named interfaces for nested props if they need to be consumed by descendants and downstream devs.

}

export const ConnectionModal: FC<ConnectionModalProps> = ({ title = 'Select connection', onCancel, ...props }) => {
const { visible, setVisible } = useConnectionModal();
const setConnection = useSetConnection();
const [expanded, setExpanded] = useState(false);

const handleCancel = useCallback(
(event: MouseEvent<HTMLElement>) => {
if (onCancel) onCancel(event);
if (!event.defaultPrevented) setVisible(false);
},
[onCancel, setVisible]
);

const handleConnectionClick = useCallback(
(
event: MouseEvent<HTMLElement>,
connection: {
name: string;
endpoint: string;
}
) => {
// TODO: Add a way to replace commitment.
setConnection(new Connection(connection.endpoint, { commitment: 'confirmed' }));
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure why this doesn't use the connection options provided by the existing ConnectionProvider context.

handleCancel(event);
},
[handleCancel]
);

const onOpenChange = useCallback(() => setExpanded(!expanded), [setExpanded, expanded]);

return (
<Modal
title={title}
visible={visible}
centered={true}
onCancel={handleCancel}
footer={null}
width={320}
bodyStyle={{ padding: 0 }}
{...props}
>
<Menu className="wallet-adapter-modal-menu" inlineIndent={0} mode="inline" onOpenChange={onOpenChange}>
{props.connections.map((connection) => (
<ConnectionMenuItem
key={connection.name}
onClick={(event) => handleConnectionClick(event, connection)}
connection={connection}
/>
))}
</Menu>
</Modal>
);
};
14 changes: 14 additions & 0 deletions packages/ui/ant-design/src/ConnectionModalProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React, { FC, useState } from 'react';
import { ConnectionModalContext } from './useConnectionModal';
import { ConnectionModal, ConnectionModalProps } from './ConnectionModal';

export const ConnectionModalProvider: FC<ConnectionModalProps> = ({ children, ...props }) => {
const [visible, setVisible] = useState(false);

return (
<ConnectionModalContext.Provider value={{ visible, setVisible }}>
{children}
<ConnectionModal {...props} />
</ConnectionModalContext.Provider>
);
};
Loading