Hi!
I am currently working on recreate the react component “WithWalletConnector” into a dash component ( concordium-dapp-libraries/packages/react-components/src/WithWalletConnector.ts at main · Concordium/concordium-dapp-libraries · GitHub )
But to start out I have made a simple example with typescript just for testing.
MyComponent.ts
import React, { Component } from 'react';
import {DashComponentProps} from './props';
type Props = {
// Insert props
} & DashComponentProps;
export default class WalletConnectionManager extends Component<Props, any> {
constructor(props: Props) {
super(props);
//this.props = props
console.log('====================================');
console.log(props);
console.log('====================================');
}
static defaultProps: {}; //Partial<Props>
render(): React.ReactNode {
return (
'SomeString'
)
}
}
WalletConnectionManager.defaultProps = {};
props.ts
*/
export type DashComponentProps = {
/**
* Unique ID to identify this component in Dash callbacks.
*/
id?: string;
/**
* Unique ID to identify this component in Dash callbacks.
*/
test?: string;
/**
* Update props to trigger callbacks.
*/
setProps: (props: Record<string, any>) => void;
}
Typescript Cookiecutter
Running this with the Typescript cookiecutter works fine, but when I convert component to the actual WithWalletConnector code, I get following error when building:
error message:
> dash-generate-components ./src/ts/components tswalletwrapper -p package-info.json --r-prefix '' --jl-prefix '' --ignore \.test\.
ERROR: "__@hasInstance@9" matches reserved word pattern: /^_.*$/
Description for Tswalletwrapper.prototype is missing!
Description for Tswalletwrapper.length is missing!
Description for Tswalletwrapper.arguments is missing!
Description for Tswalletwrapper.caller is missing!
ERROR: "__@hasInstance@9" matches reserved word pattern: /^_.*$/
Description for Tswalletwrapper is missing!
extract-meta failed
I have not been able to find any information around this error. (And as a comment the typescript cookiecutter would be nice if it included a “run demo” option as in the original.)
My component (leading to above error) looks as follows:
import React, { Component } from 'react';
import {DashComponentProps} from '../props';
import { Network, WalletConnection, WalletConnectionDelegate, WalletConnector } from '@concordium/react-components';
import { errorString } from './error';
/**
* Activation/deactivation controller of a given connector type.
*/
export interface ConnectorType {
/**
* Called when the connection type is being activated.
* The connector instance returned by this method becomes the new {@link State.activeConnector activeConnector}.
* @param component The component in which the instance is being activated.
* This object doubles as the delegate to pass to new connector instances.
* @param network The network to pass to new connector instances.
*/
activate(component: WalletConnectionManager, network: Network): Promise<WalletConnector>;
/**
* Called from {@link WithWalletConnector} when the connection type is being deactivated,
* i.e. right after {@link State.activeConnector activeConnector} has been unset from this value.
* @param component The component in which the instance is being deactivated.
* @param connector The connector to deactivate.
*/
deactivate(component: WalletConnectionManager, connector: WalletConnector): Promise<void>;
}
/**
* Produce a {@link ConnectorType} that creates a new connector instance on activation
* and disconnects the existing one on deactivation.
* This is the simplest connection type and should be used unless there's a reason not to.
* @param create Factory function for creating new connector instances.
*/
export function ephemeralConnectorType(create: (c: WalletConnectionManager, n: Network) => Promise<WalletConnector>) {
return {
activate: create,
deactivate: (w: WalletConnectionManager, c: WalletConnector) => c.disconnect(),
};
}
/**
* Produce a {@link ConnectorType} that reuse connectors between activation cycles.
* That is, once a connector is created, it's never automatically disconnected.
* Note that only the connector is permanent. Individual connections may still be disconnected by the application.
* @param create Factory function for creating new connector instances.
*/
export function persistentConnectorType(create: (c: WalletConnectionManager, n: Network) => Promise<WalletConnector>) {
const connectorPromises = new Map<WalletConnectionManager, Map<Network, Promise<WalletConnector>>>();
return {
activate: (component: WalletConnectionManager, network: Network) => {
const delegateConnectorPromises =
connectorPromises.get(component) || new Map<Network, Promise<WalletConnector>>();
connectorPromises.set(component, delegateConnectorPromises);
const connectorPromise = delegateConnectorPromises.get(network) || create(component, network);
delegateConnectorPromises.set(network, connectorPromise);
return connectorPromise;
},
deactivate: async () => undefined,
};
}
/**
* The internal state of the component.
*/
interface State {
/**
* The active connector type. This value is updated using {@link WalletConnectionProps.setActiveConnectorType}.
* Changes to this value trigger activation of a connector managed by the connector type.
* This will cause {@link activeConnector} or {@link activeConnectorError} to change depending on the outcome.
*/
activeConnectorType: ConnectorType | undefined;
/**
* The active connector. Connector instances get (de)activated appropriately when {@link activeConnectorType} changes.
*
* It's up to the {@link ConnectorType} in {@link activeConnectorType} to implement any synchronization between
* the active connector and {@link activeConnector}:
* In general, it is perfectly possible for the active connection to not originate from the active connector.
*
* If the application disconnects the active connector manually, they must also call
* {@link WalletConnectionProps.setActiveConnectorType} to
*/
activeConnector: WalletConnector | undefined;
/**
* Any of the following kinds of errors:
* - Error activating a connector with {@link activeConnectorType}.
* In this case {@link activeConnector} is undefined.
* - Error deactivating the previous connector.
* In this case {@link activeConnectorType} and {@link activeConnector} are undefined.
*/
activeConnectorError: string;
/**
* A map from open connections to their selected accounts or the empty string
* if the connection doesn't have an associated account.
*/
connectedAccounts: Map<WalletConnection, string>;
/**
* A map from open connections to the hash of the genesis block for the chain that the selected accounts
* of the connections live on.
* Connections without a selected account (or the account's chain is unknown) will not have an entry in this map.
*
* TODO The reported hash values are not too reliable as they're updated only when the `onChainChanged` event fires.
* And this doesn't happen when the connection is initiated.
* For WalletConnect we could do that manually as we control what chain we connect to.
* For BrowserWallet we don't have that option (see also https://concordium.atlassian.net/browse/CBW-633).
*/
genesisHashes: Map<WalletConnection, string>;
}
function updateMapEntry<K, V>(map: Map<K, V>, key: K | undefined, value: V | undefined) {
const res = new Map(map);
if (key !== undefined) {
if (value !== undefined) {
res.set(key, value);
} else {
res.delete(key);
}
}
return res;
}
/**
* PROPS definition
*/
/* type Props = {
// Insert props
} & DashComponentProps; */
interface Props extends DashComponentProps {
/**
* The network on which the connected accounts are expected to live on.
*
* Changes to this value will cause all connections managed by {@link State.activeConnector} to get disconnected.
*/
network: Network; // reacting to change in 'componentDidUpdate'
/**
* Function for generating the child component based on the props derived from the state of this component.
*
* JSX automatically supplies the nested expression as this prop field, so callers usually don't set it explicitly.
*
* @param props Connection state and management functions.
* @return Child component.
*/
children: (props: WalletConnectionProps) => JSX.Element;
};
/**
* The props to be passed to the child component.
*/
export interface WalletConnectionProps extends State {
/**
* The network provided to {@link WithWalletConnector} via its props.
*
* This is only passed for convenience as the value is always available to the child component anyway.
*/
network: Network;
/**
* Function for setting or resetting {@link State.activeConnectorType activeConnectorType}.
*
* Any existing connector type value is deactivated and any new one is activated.
*
* @param type The new connector type or undefined to reset the value.
*/
setActiveConnectorType: (type: ConnectorType | undefined) => void;
}
/**
* This is the actual class exported to Dash. It is a rebuild of the original WithWalletConnector in order
* to get access to the different variables without an extra wrapper.
*/
class WalletConnectionManager extends Component<Props, State> implements WalletConnectionDelegate {
constructor(props: Props) {
super(props);
this.state = {
activeConnectorType: undefined,
activeConnector: undefined,
activeConnectorError: '',
genesisHashes: new Map(),
connectedAccounts: new Map(),
};
}
static defaultProps: {};
/**
* @see WalletConnectionProps.setActiveConnectorType
*/
setActiveConnectorType = (type: ConnectorType | undefined) => {
console.debug("WithWalletConnector: calling 'setActiveConnectorType'", { type, state: this.state });
const { network } = this.props;
const { activeConnectorType, activeConnector } = this.state;
this.setState({
activeConnectorType: type,
activeConnector: undefined,
activeConnectorError: '',
});
if (activeConnectorType && activeConnector) {
activeConnectorType.deactivate(this, activeConnector).catch((err) =>
this.setState((state) => {
// Don't set error if user switched connector type since initializing this connector.
// It's OK to show it if the user switched away and back...
if (state.activeConnectorType !== type) {
return state;
}
return { ...state, activeConnectorError: errorString(err) };
})
);
}
if (type) {
type.activate(this, network)
.then((connector: WalletConnector) => {
console.log('WithWalletConnector: setting active connector', { connector });
// Switch the connector (type) back in case the user changed it since initiating the connection.
this.setState({ activeConnectorType: type, activeConnector: connector, activeConnectorError: '' });
})
.catch((err) =>
this.setState((state) => {
if (state.activeConnectorType !== type) {
return state;
}
return { ...state, activeConnectorError: errorString(err) };
})
);
}
};
onAccountChanged = (connection: WalletConnection, address: string | undefined) => {
console.debug("WithWalletConnector: calling 'onAccountChanged'", { connection, address, state: this.state });
this.setState((state) => ({
...state,
connectedAccounts: updateMapEntry(state.connectedAccounts, connection, address || ''),
}));
};
onChainChanged = (connection: WalletConnection, genesisHash: string) => {
console.debug("WithWalletConnector: calling 'onChainChanged'", { connection, genesisHash, state: this.state });
this.setState((state) => ({
...state,
genesisHashes: updateMapEntry(state.genesisHashes, connection, genesisHash),
}));
};
onConnected = (connection: WalletConnection, address: string | undefined) => {
console.debug("WithWalletConnector: calling 'onConnected'", { connection, state: this.state });
this.onAccountChanged(connection, address);
};
onDisconnected = (connection: WalletConnection) => {
console.debug("WithWalletConnector: calling 'onDisconnected'", { connection, state: this.state });
this.setState((state) => ({
...state,
connectedAccounts: updateMapEntry(state.connectedAccounts, connection, undefined),
}));
};
render() {
const { children, network } = this.props;
//return children({ ...this.state, network, setActiveConnectorType: this.setActiveConnectorType });
return (
<div>
{children({ ...this.state, network, setActiveConnectorType: this.setActiveConnectorType })};
</div>
)
};
componentDidUpdate(prevProps: Props) {
if (prevProps.network !== this.props.network) {
// Reset active connector and connection when user changes network.
// In the future there may be a mechanism for negotiating with the wallet.
this.setActiveConnectorType(undefined);
}
}
componentWillUnmount() {
// TODO Disconnect everything?
}
}
WalletConnectionManager.defaultProps = {};
export default WalletConnectionManager;
Standard Cookiecutter
So as I did not get any further with this I tried with the original template:
Here running the demo example works as intended, but when I try to build it, I notice that it does not create the python module file as expected but no error messages, see output below:
Actual output:
> dash-generate-components ./src/lib/components walletconnectionmanager -p package-info.json --r-prefix '' --jl-prefix '' --ignore \.test\.
<-- HERE -->
Warning: a URL for bug reports was not provided. Empty string inserted.
Warning: a homepage URL was not provided. Empty string inserted.
Generated src/Walletconnectionmanager.jl
Generated Project.toml
Done in 1.85s.
Expected output:
> dash-generate-components ./src/lib/components my_dash_component -p package-info.json --r-prefix '' --jl-prefix '' --ignore \.test\.
Generated MyDashComponent.py
Generated myDashComponent.R
Generated mydashcomponent.jl
Warning: a URL for bug reports was not provided. Empty string inserted.
Warning: a homepage URL was not provided. Empty string inserted.
Generated src/MyDashComponent.jl
Generated Project.toml
Done in 2.07s.
In order to run typescript with the standard cookiecutter I have made following changes/configurations:
- .babelrc - Inserted following in presets: “@babel/preset-typescript”
- webpack.config.js - changed rule test arg from
test: /\.jsx?$/,
totest: /\.(js|jsx|ts|tsx)$/,
- Installed - “@babel/preset-typescript”
Any help would be highly appreciated!