From c685d17c5ddd68f2b205927aa5b1224e8cffbb48 Mon Sep 17 00:00:00 2001 From: Noah Zinsmeister Date: Mon, 25 Apr 2022 13:32:01 -0400 Subject: [PATCH] do some type cleanup in core (#527) * first pass * export the type * omit Web3Provider type, some cleanup * comments --- packages/core/src/hooks.ts | 62 +++++++++++++++++++++++----------- packages/core/src/provider.tsx | 24 +++++++++---- 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/packages/core/src/hooks.ts b/packages/core/src/hooks.ts index 410d8f2..d8dcce1 100644 --- a/packages/core/src/hooks.ts +++ b/packages/core/src/hooks.ts @@ -36,7 +36,7 @@ export function initializeConnector( const stateHooks = getStateHooks(useConnector) const derivedHooks = getDerivedHooks(stateHooks) - const augmentedHooks = getAugmentedHooks(connector, stateHooks, derivedHooks) + const augmentedHooks = getAugmentedHooks(connector, stateHooks, derivedHooks) return [connector, { ...stateHooks, ...derivedHooks, ...augmentedHooks }, store] } @@ -104,7 +104,15 @@ export function getSelectedConnector( return values[getIndex(connector)] } - function useSelectedProvider(connector: Connector, network?: Networkish) { + /** + * @typeParam T - A type argument must only be provided if one or more of the connectors passed to + * getSelectedConnector is using `connector.customProvider`, in which case it must match every possible type of this + * property, over all connectors. + */ + function useSelectedProvider( + connector: Connector, + network?: Networkish + ): T | undefined { const index = getIndex(connector) // eslint-disable-next-line react-hooks/rules-of-hooks const values = initializedConnectors.map(([, { useProvider }], i) => useProvider(network, i === index)) @@ -199,6 +207,11 @@ export function getPriorityConnector( return useSelectedIsActive(usePriorityConnector()) } + /** + * @typeParam T - A type argument must only be provided if one or more of the connectors passed to + * getPriorityConnector is using `connector.customProvider`, in which case it must match every possible type of this + * property, over all connectors. + */ function usePriorityProvider(network?: Networkish) { return useSelectedProvider(usePriorityConnector(), network) } @@ -317,34 +330,45 @@ function useENS(provider?: BaseProvider, accounts?: string[]): (string | null)[] function getAugmentedHooks( connector: T, - { useChainId, useAccounts }: ReturnType, + { useAccounts }: ReturnType, { useAccount, useIsActive }: ReturnType ) { - // avoid type erasure by returning the most qualified type if not otherwise set - function useProvider( - network?: Networkish, - enabled = true - ): Web3Provider | undefined | T { + /** + * Avoid type erasure by returning the most qualified type if not otherwise set. + * Note that this function's return type is `T | undefined`, but there is a code path + * that returns a Web3Provider, which could conflict with a user-provided T. So, + * it's important that users only provide an override for T if they know that + * `connector.customProvider` is going to be defined and of type T. + * + * @typeParam T - A type argument must only be provided if using `connector.customProvider`, in which case it + * must match the type of this property. + */ + function useProvider(network?: Networkish, enabled = true): T | undefined { const isActive = useIsActive() - const chainId = useChainId() - const accounts = useAccounts() - // trigger the dynamic import on mount - const [providers, setProviders] = useState<{ Web3Provider: typeof Web3Provider } | undefined>(undefined) + // we store the class in an object and then destructure to avoid a compiler error related to class instantiation + const [{ DynamicWeb3Provider }, setDynamicWeb3Provider] = useState<{ + DynamicWeb3Provider: typeof Web3Provider | undefined + }>({ + DynamicWeb3Provider: undefined, + }) useEffect(() => { - import('@ethersproject/providers').then(setProviders).catch(() => { - console.debug('@ethersproject/providers not available') - }) + import('@ethersproject/providers') + .then(({ Web3Provider }) => setDynamicWeb3Provider({ DynamicWeb3Provider: Web3Provider })) + .catch(() => { + console.debug('@ethersproject/providers not available') + }) }, []) return useMemo(() => { - // we use chainId and accounts to re-render in case connector.{customProvider,provider} change in place - if (providers && enabled && isActive && chainId && accounts) { + // to ensure connectors remain fresh after network changes, we use isActive here to ensure re-renders + if (DynamicWeb3Provider && enabled && isActive) { if (connector.customProvider) return connector.customProvider as T - if (connector.provider) return new providers.Web3Provider(connector.provider, network) + // see tsdoc note above for return type explanation. + if (connector.provider) return new DynamicWeb3Provider(connector.provider, network) as unknown as T } - }, [providers, enabled, isActive, chainId, accounts, network]) + }, [DynamicWeb3Provider, enabled, isActive, network]) } function useENSNames(provider?: BaseProvider): (string | null)[] | undefined { diff --git a/packages/core/src/provider.tsx b/packages/core/src/provider.tsx index 82c0277..44da7ab 100644 --- a/packages/core/src/provider.tsx +++ b/packages/core/src/provider.tsx @@ -1,12 +1,17 @@ import type { Networkish } from '@ethersproject/networks' import type { BaseProvider, Web3Provider } from '@ethersproject/providers' import type { Connector } from '@web3-react/types' -import type { ReactNode } from 'react' +import type { Context, ReactNode } from 'react' import React, { createContext, useContext } from 'react' import type { Web3ReactHooks, Web3ReactPriorityHooks } from './hooks' import { getPriorityConnector } from './hooks' -type Web3ContextType = { +/** + * @typeParam T - A type argument must only be provided if one or more of the connectors passed to Web3ReactProvider + * is using `connector.customProvider`, in which case it must match every possible type of this + * property, over all connectors. + */ +export type Web3ContextType = { connector: ReturnType chainId: ReturnType accounts: ReturnType @@ -14,14 +19,14 @@ type Web3ContextType = { error: ReturnType account: ReturnType isActive: ReturnType - provider: ReturnType + provider: T | undefined ENSNames: ReturnType ENSName: ReturnType } const Web3Context = createContext(undefined) -export function Web3ReactProvider({ +export function Web3ReactProvider({ children, connectors, network, @@ -52,7 +57,12 @@ export function Web3ReactProvider({ const error = usePriorityError() const account = usePriorityAccount() const isActive = usePriorityIsActive() - const provider = usePriorityProvider(network) + // note that we've omitted a generic type + // in Web3ReactProvider, and thus can't pass T through to usePriorityProvider below. + // this is because if we did so, the type of provider would include T, but that would + // conflict because Web3Context can't take a generic. however, this isn't particularly + // important, because useWeb3React (below) is manually typed + const provider = usePriorityProvider(network) const ENSNames = usePriorityENSNames(lookupENS ? provider : undefined) const ENSName = usePriorityENSName(lookupENS ? provider : undefined) @@ -76,8 +86,8 @@ export function Web3ReactProvider({ ) } -export function useWeb3React() { - const web3 = useContext(Web3Context) +export function useWeb3React() { + const web3 = useContext(Web3Context as Context | undefined>) if (!web3) throw Error('useWeb3React can only be used within the Web3ReactProvider component') return web3 }