Skip to content

Commit

Permalink
do some type cleanup in core (#527)
Browse files Browse the repository at this point in the history
* first pass

* export the type

* omit Web3Provider type, some cleanup

* comments
  • Loading branch information
NoahZinsmeister authored Apr 25, 2022
1 parent 3f9e639 commit c685d17
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 26 deletions.
62 changes: 43 additions & 19 deletions packages/core/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function initializeConnector<T extends Connector>(

const stateHooks = getStateHooks(useConnector)
const derivedHooks = getDerivedHooks(stateHooks)
const augmentedHooks = getAugmentedHooks(connector, stateHooks, derivedHooks)
const augmentedHooks = getAugmentedHooks<T>(connector, stateHooks, derivedHooks)

return [connector, { ...stateHooks, ...derivedHooks, ...augmentedHooks }, store]
}
Expand Down Expand Up @@ -104,7 +104,15 @@ export function getSelectedConnector(
return values[getIndex(connector)]
}

function useSelectedProvider<T extends BaseProvider = Web3Provider>(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<T extends BaseProvider = Web3Provider>(
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<T>(network, i === index))
Expand Down Expand Up @@ -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<T extends BaseProvider = Web3Provider>(network?: Networkish) {
return useSelectedProvider<T>(usePriorityConnector(), network)
}
Expand Down Expand Up @@ -317,34 +330,45 @@ function useENS(provider?: BaseProvider, accounts?: string[]): (string | null)[]

function getAugmentedHooks<T extends Connector>(
connector: T,
{ useChainId, useAccounts }: ReturnType<typeof getStateHooks>,
{ useAccounts }: ReturnType<typeof getStateHooks>,
{ useAccount, useIsActive }: ReturnType<typeof getDerivedHooks>
) {
// avoid type erasure by returning the most qualified type if not otherwise set
function useProvider<T extends BaseProvider = Web3Provider>(
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<T extends BaseProvider = Web3Provider>(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 {
Expand Down
24 changes: 17 additions & 7 deletions packages/core/src/provider.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
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<T extends BaseProvider = Web3Provider> = {
connector: ReturnType<Web3ReactPriorityHooks['usePriorityConnector']>
chainId: ReturnType<Web3ReactPriorityHooks['usePriorityChainId']>
accounts: ReturnType<Web3ReactPriorityHooks['usePriorityAccounts']>
isActivating: ReturnType<Web3ReactPriorityHooks['usePriorityIsActivating']>
error: ReturnType<Web3ReactPriorityHooks['usePriorityError']>
account: ReturnType<Web3ReactPriorityHooks['usePriorityAccount']>
isActive: ReturnType<Web3ReactPriorityHooks['usePriorityIsActive']>
provider: ReturnType<Web3ReactPriorityHooks['usePriorityProvider']>
provider: T | undefined
ENSNames: ReturnType<Web3ReactPriorityHooks['usePriorityENSNames']>
ENSName: ReturnType<Web3ReactPriorityHooks['usePriorityENSName']>
}

const Web3Context = createContext<Web3ContextType | undefined>(undefined)

export function Web3ReactProvider<T extends BaseProvider = Web3Provider>({
export function Web3ReactProvider({
children,
connectors,
network,
Expand Down Expand Up @@ -52,7 +57,12 @@ export function Web3ReactProvider<T extends BaseProvider = Web3Provider>({
const error = usePriorityError()
const account = usePriorityAccount()
const isActive = usePriorityIsActive()
const provider = usePriorityProvider<T>(network)
// note that we've omitted a <T extends BaseProvider = Web3Provider> 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)

Expand All @@ -76,8 +86,8 @@ export function Web3ReactProvider<T extends BaseProvider = Web3Provider>({
)
}

export function useWeb3React() {
const web3 = useContext(Web3Context)
export function useWeb3React<T extends BaseProvider = Web3Provider>() {
const web3 = useContext(Web3Context as Context<Web3ContextType<T> | undefined>)
if (!web3) throw Error('useWeb3React can only be used within the Web3ReactProvider component')
return web3
}

0 comments on commit c685d17

Please sign in to comment.