diff --git a/packages/react/package.json b/packages/react/package.json index df20c5b95..cf8e0efa2 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/react", - "version": "2.1.7", + "version": "2.1.7-alpha.1", "description": "Collection of React Hooks for web3-onboard", "module": "dist/index.js", "browser": "dist/index.js", @@ -17,14 +17,17 @@ }, "license": "MIT", "devDependencies": { - "@types/react": "^17.0.39", + "@types/react": "^18.0.2", + "@types/use-sync-external-store": "^0.0.3", + "react": "^18.0.0", "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/core": "^2.2.9", - "@web3-onboard/common": "^2.1.0" + "@web3-onboard/core": "^2.2.10", + "@web3-onboard/common": "^2.1.0-alpha.1", + "use-sync-external-store": "1.0.0" }, "peerDependencies": { - "react": "^17.0.2" + "react": ">=16.8" } -} +} \ No newline at end of file diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 23d9e7c7a..9d83b45bf 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,4 +1,5 @@ -import { useEffect, useState, useCallback, useMemo } from 'react' +import { useState, useCallback } from 'react' +import { useSyncExternalStore } from 'use-sync-external-store/shim' import Web3Onboard from '@web3-onboard/core' import type { @@ -9,8 +10,8 @@ import type { WalletState, ConnectedChain } from '@web3-onboard/core' - -import { Chain } from '@web3-onboard/common' +import type { Chain } from '@web3-onboard/common' +import type { AppState } from '@web3-onboard/core/dist/types' export let web3Onboard: OnboardAPI | null = null @@ -19,43 +20,62 @@ export const init = (options: InitOptions): OnboardAPI => { return web3Onboard } +const HOOK_ERROR_MESSAGE = 'Must initialize before using hooks.' + +const useAppState: { + (): AppState + (stateKey?: K): AppState[K] +} = (stateKey = undefined) => { + if (!web3Onboard) throw new Error(HOOK_ERROR_MESSAGE) + + const { select, get } = web3Onboard.state + + const subscribe = useCallback( + (onStoreChange: () => void) => { + const { unsubscribe } = stateKey + ? select(stateKey).subscribe(onStoreChange) + : select().subscribe(onStoreChange) + + return () => unsubscribe + }, + [stateKey] + ) + + const getSnapshot = useCallback(() => { + const snapshot = get() + return stateKey ? snapshot[stateKey] : snapshot + }, [stateKey]) + + return useSyncExternalStore(subscribe, getSnapshot) +} + export const useConnectWallet = (): [ { wallet: WalletState | null; connecting: boolean }, - (options: ConnectOptions) => Promise, + (options?: ConnectOptions) => Promise, (wallet: DisconnectOptions) => Promise ] => { - if (!web3Onboard) throw new Error('Must initialize before using hooks.') + if (!web3Onboard) throw new Error(HOOK_ERROR_MESSAGE) - const [wallet, setConnectedWallet] = useState( - () => (web3Onboard as OnboardAPI).state.get().wallets[0] || null - ) - const [connecting, setConnecting] = useState(false) + const { connectWallet, disconnectWallet } = web3Onboard - useEffect(() => { - const subscription = (web3Onboard as OnboardAPI).state - .select('wallets') - .subscribe(wallets => setConnectedWallet(wallets[0] || null)) + const wallets = useAppState('wallets') + const wallet = wallets[0] || null - return () => subscription.unsubscribe() - }, [wallet]) + const [connecting, setConnecting] = useState(false) - const connect = useCallback(async (options: ConnectOptions) => { + const connect = useCallback(async (options?: ConnectOptions) => { setConnecting(true) - const [connectedWallet] = await (web3Onboard as OnboardAPI).connectWallet( - options - ) + await connectWallet(options) setConnecting(false) - setConnectedWallet(connectedWallet || null) }, []) - const disconnect = useCallback(async ({ label }) => { + const disconnect = useCallback(async ({ label }: DisconnectOptions) => { setConnecting(true) - await (web3Onboard as OnboardAPI).disconnectWallet({ label }) + await disconnectWallet({ label }) - setConnectedWallet(null) setConnecting(false) }, []) @@ -77,63 +97,35 @@ export const useSetChain = ( }, (options: SetChainOptions) => Promise ] => { - if (!web3Onboard) throw new Error('Must initialize before using hooks.') + if (!web3Onboard) throw new Error(HOOK_ERROR_MESSAGE) - const { state, setChain } = web3Onboard as OnboardAPI - const [settingChain, setInProgress] = useState(false) - - const [connectedChain, setConnectedChain] = useState( - () => { - const initialWallets = (web3Onboard as OnboardAPI).state.get().wallets - if (initialWallets.length === 0) return null - return ( - ( - initialWallets.find(({ label }) => label === walletLabel) || - initialWallets[0] - ).chains[0] || null - ) - } - ) + const { setChain } = web3Onboard - const chains = useMemo(() => state.get().chains, []) + const { wallets, chains } = useAppState() - useEffect(() => { - const subscription = state.select('wallets').subscribe(wallets => { - const wallet = - wallets.find(({ label }) => label === walletLabel) || wallets[0] + const connectedChain = + (walletLabel + ? wallets.find(({ label }) => label === walletLabel) + : wallets[0] + )?.chains[0] || null - wallet && setConnectedChain(wallet.chains[0]) - }) - - return () => subscription.unsubscribe() - }, []) + const [settingChain, setInProgress] = useState(false) - const set = useCallback(async (options: SetChainOptions): Promise => { + const set = useCallback(async (options: SetChainOptions) => { setInProgress(true) const success = await setChain({ ...options, wallet: walletLabel }) setInProgress(false) - return success; + return success }, []) return [{ chains, connectedChain, settingChain }, set] } export const useWallets = (): WalletState[] => { - if (!web3Onboard) throw new Error('Must initialize before using hooks.') - - const [wallets, setConnectedWallets] = useState( - () => (web3Onboard as OnboardAPI).state.get().wallets - ) - - useEffect(() => { - const wallets$ = (web3Onboard as OnboardAPI).state.select('wallets') - const subscription = wallets$.subscribe(setConnectedWallets) - - return () => subscription.unsubscribe() - }, []) + if (!web3Onboard) throw new Error(HOOK_ERROR_MESSAGE) - return wallets + return useAppState('wallets') } diff --git a/yarn.lock b/yarn.lock index 67f99341a..ce95f5559 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1649,10 +1649,10 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react@^17.0.39": - version "17.0.43" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55" - integrity sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A== +"@types/react@^18.0.2": + version "18.0.9" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.9.tgz#d6712a38bd6cd83469603e7359511126f122e878" + integrity sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -1711,6 +1711,11 @@ dependencies: "@types/node" "*" +"@types/use-sync-external-store@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" + integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== + "@types/ws@^8.2.2": version "8.2.3" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.3.tgz#0bca6b03ba2f41e0fab782d4a573fe284aa907ae" @@ -7782,6 +7787,13 @@ react@16.13.1: object-assign "^4.1.1" prop-types "^15.6.2" +react@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" + integrity sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A== + dependencies: + loose-envify "^1.1.0" + readable-stream@^1.0.33: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -9218,6 +9230,11 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +use-sync-external-store@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.0.0.tgz#d98f4a9c2e73d0f958e7e2d2c2bfb5f618cbd8fd" + integrity sha512-AFVsxg5GkFg8GDcxnl+Z0lMAz9rE8DGJCc28qnBuQF7lac57B5smLcT37aXpXIIPz75rW4g3eXHPjhHwdGskOw== + utf-8-validate@^5.0.2: version "5.0.8" resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.8.tgz#4a735a61661dbb1c59a0868c397d2fe263f14e58"