diff --git a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx index 66f88a80f9..bba2c13940 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetForm.tsx @@ -86,7 +86,7 @@ export function TradeWidgetForm(props: TradeWidgetProps) { const areCurrenciesLoading = !inputCurrencyInfo.currency && !outputCurrencyInfo.currency const bothCurrenciesSet = !!inputCurrencyInfo.currency && !!outputCurrencyInfo.currency - const hasRecipientInUrl = !!tradeStateFromUrl.recipient + const hasRecipientInUrl = !!tradeStateFromUrl?.recipient const withRecipient = !isWrapOrUnwrap && (showRecipient || hasRecipientInUrl) const maxBalance = maxAmountSpend(inputCurrencyInfo.balance || undefined, isSafeWallet) const showSetMax = maxBalance?.greaterThan(0) && !inputCurrencyInfo.amount?.equalTo(maxBalance) diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/setupTradeState/useSetupTradeState.ts b/apps/cowswap-frontend/src/modules/trade/hooks/setupTradeState/useSetupTradeState.ts index 9b7ac812d3..d32a24f458 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/setupTradeState/useSetupTradeState.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/setupTradeState/useSetupTradeState.ts @@ -11,6 +11,7 @@ import { useIsAlternativeOrderModalVisible } from 'modules/trade/state/alternati import { getDefaultTradeRawState, TradeRawState } from 'modules/trade/types/TradeRawState' import { useResetStateWithSymbolDuplication } from './useResetStateWithSymbolDuplication' +import { useSetupTradeStateFromUrl } from './useSetupTradeStateFromUrl' import { useTradeStateFromUrl } from './useTradeStateFromUrl' import { useTradeState } from '../useTradeState' @@ -19,6 +20,7 @@ const INITIAL_CHAIN_ID_FROM_URL = getRawCurrentChainIdFromUrl() const EMPTY_TOKEN_ID = '_' export function useSetupTradeState(): void { + useSetupTradeStateFromUrl() const { chainId: providerChainId, account } = useWalletInfo() const prevProviderChainId = usePrevious(providerChainId) @@ -35,7 +37,7 @@ export function useSetupTradeState(): void { const [isFirstLoad, setIsFirstLoad] = useState(true) const isWalletConnected = !!account - const urlChainId = tradeStateFromUrl.chainId + const urlChainId = tradeStateFromUrl?.chainId const prevTradeStateFromUrl = usePrevious(tradeStateFromUrl) const currentChainId = !urlChainId ? prevProviderChainId || SupportedChainId.MAINNET : urlChainId @@ -52,7 +54,7 @@ export function useSetupTradeState(): void { console.error('Network switching error: ', error) }) }, - [switchNetwork] + [switchNetwork], ) const debouncedSwitchNetworkInWallet = debounce(([targetChainId]: [SupportedChainId]) => { @@ -113,6 +115,10 @@ export function useSetupTradeState(): void { if (isAlternativeModalVisible) { return } + // Not loaded yet, ignore + if (!tradeStateFromUrl) { + return + } const { inputCurrencyId, outputCurrencyId } = tradeStateFromUrl const providerAndUrlChainIdMismatch = currentChainId !== prevProviderChainId @@ -143,7 +149,7 @@ export function useSetupTradeState(): void { console.debug( '[TRADE STATE]', 'Remembering a new state from URL while changing chainId in provider', - tradeStateFromUrl + tradeStateFromUrl, ) return diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/setupTradeState/useSetupTradeStateFromUrl.ts b/apps/cowswap-frontend/src/modules/trade/hooks/setupTradeState/useSetupTradeStateFromUrl.ts new file mode 100644 index 0000000000..f6a38792dd --- /dev/null +++ b/apps/cowswap-frontend/src/modules/trade/hooks/setupTradeState/useSetupTradeStateFromUrl.ts @@ -0,0 +1,41 @@ +import { useSetAtom } from 'jotai' +import { useEffect } from 'react' + +import { useLocation, useParams } from 'react-router-dom' + +import { tradeStateFromUrlAtom } from 'modules/trade/state/tradeStateFromUrlAtom' + +import { TradeRawState } from '../../types/TradeRawState' + +/** + * Updater to fetch trade state from URL params and query, and store it on jotai state + * /1/swap/WETH/DAI?recipient=0x -> { chainId: 1, inputCurrencyId: 'WETH', outputCurrencyId: 'DAI', recipient: '0x' } + * + * Load this hook only once to avoid unnecessary re-renders + */ +export function useSetupTradeStateFromUrl(): null { + const params = useParams() + const location = useLocation() + const stringifiedParams = JSON.stringify(params) + const setState = useSetAtom(tradeStateFromUrlAtom) + + useEffect(() => { + const searchParams = new URLSearchParams(location.search) + const recipient = searchParams.get('recipient') + const recipientAddress = searchParams.get('recipientAddress') + const { chainId, inputCurrencyId, outputCurrencyId } = JSON.parse(stringifiedParams) + const chainIdAsNumber = chainId && /^\d+$/.test(chainId) ? parseInt(chainId) : null + + const state: TradeRawState = { + chainId: chainIdAsNumber, + inputCurrencyId: inputCurrencyId || searchParams.get('inputCurrency') || null, + outputCurrencyId: outputCurrencyId || searchParams.get('outputCurrency') || null, + ...(recipient ? { recipient } : undefined), + ...(recipientAddress ? { recipientAddress } : undefined), + } + + setState(state) + }, [location.search, stringifiedParams, setState]) + + return null +} diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/setupTradeState/useTradeStateFromUrl.ts b/apps/cowswap-frontend/src/modules/trade/hooks/setupTradeState/useTradeStateFromUrl.ts index 4ae21e98bf..91204e677f 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/setupTradeState/useTradeStateFromUrl.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/setupTradeState/useTradeStateFromUrl.ts @@ -1,6 +1,6 @@ -import { useMemo } from 'react' +import { useAtomValue } from 'jotai' -import { useLocation, useParams } from 'react-router-dom' +import { tradeStateFromUrlAtom } from 'modules/trade/state/tradeStateFromUrlAtom' import { TradeRawState } from '../../types/TradeRawState' @@ -8,26 +8,6 @@ import { TradeRawState } from '../../types/TradeRawState' * Get trade state from URL params and query * /1/swap/WETH/DAI?recipient=0x -> { chainId: 1, inputCurrencyId: 'WETH', outputCurrencyId: 'DAI', recipient: '0x' } */ -export function useTradeStateFromUrl(): TradeRawState { - const params = useParams() - const location = useLocation() - - return useMemo(() => { - const searchParams = new URLSearchParams(location.search) - const recipient = searchParams.get('recipient') - const recipientAddress = searchParams.get('recipientAddress') - const { chainId, inputCurrencyId, outputCurrencyId } = params - const chainIdAsNumber = chainId && /^\d+$/.test(chainId) ? parseInt(chainId) : null - - const state: TradeRawState = { - chainId: chainIdAsNumber, - inputCurrencyId: inputCurrencyId || searchParams.get('inputCurrency') || null, - outputCurrencyId: outputCurrencyId || searchParams.get('outputCurrency') || null, - ...(recipient ? { recipient } : undefined), - ...(recipientAddress ? { recipientAddress } : undefined), - } - - return state - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [location.search, JSON.stringify(params)]) +export function useTradeStateFromUrl(): TradeRawState | null { + return useAtomValue(tradeStateFromUrlAtom) } diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useNotifyWidgetTrade.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useNotifyWidgetTrade.ts index 9124400e15..297fd637bb 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useNotifyWidgetTrade.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useNotifyWidgetTrade.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useEffect, useRef } from 'react' import { getCurrencyAddress } from '@cowprotocol/common-utils' import { AtomsAndUnits, CowEvents, OnTradeParamsPayload } from '@cowprotocol/events' @@ -14,10 +14,17 @@ import { TradeDerivedState } from '../types/TradeDerivedState' export function useNotifyWidgetTrade() { const state = useDerivedTradeState() + const isFirstLoad = useRef(true) useEffect(() => { - if (!state?.tradeType) return + if (isFirstLoad.current && !!state) { + isFirstLoad.current = false + return + } + if (!state?.tradeType) { + return + } EVENT_EMITTER.emit(CowEvents.ON_CHANGE_TRADE_PARAMS, getTradeParamsEventPayload(state.tradeType, state)) }, [state]) } diff --git a/apps/cowswap-frontend/src/modules/trade/hooks/useResetRecipient.ts b/apps/cowswap-frontend/src/modules/trade/hooks/useResetRecipient.ts index e934235720..79ccb97c1c 100644 --- a/apps/cowswap-frontend/src/modules/trade/hooks/useResetRecipient.ts +++ b/apps/cowswap-frontend/src/modules/trade/hooks/useResetRecipient.ts @@ -10,17 +10,17 @@ import { useDerivedTradeState } from './useDerivedTradeState' import { useIsAlternativeOrderModalVisible } from '../state/alternativeOrder' - export function useResetRecipient(onChangeRecipient: (recipient: string | null) => void): null { const isAlternativeOrderModalVisible = useIsAlternativeOrderModalVisible() const tradeState = useDerivedTradeState() const tradeStateFromUrl = useTradeStateFromUrl() const postHooksRecipientOverride = usePostHooksRecipientOverride() + const hasTradeState = !!tradeStateFromUrl const { chainId } = useWalletInfo() const prevPostHooksRecipientOverride = usePrevious(postHooksRecipientOverride) const recipient = tradeState?.recipient - const hasRecipientInUrl = !!tradeStateFromUrl.recipient + const hasRecipientInUrl = !!tradeStateFromUrl?.recipient /** * Reset recipient value only once at App start if it's not set in URL @@ -30,7 +30,7 @@ export function useResetRecipient(onChangeRecipient: (recipient: string | null) onChangeRecipient(null) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + }, [hasTradeState]) /** * Reset recipient whenever chainId changes diff --git a/apps/cowswap-frontend/src/modules/trade/state/tradeStateFromUrlAtom.ts b/apps/cowswap-frontend/src/modules/trade/state/tradeStateFromUrlAtom.ts new file mode 100644 index 0000000000..5c3ba30ef1 --- /dev/null +++ b/apps/cowswap-frontend/src/modules/trade/state/tradeStateFromUrlAtom.ts @@ -0,0 +1,5 @@ +import { atom } from 'jotai' + +import { TradeRawState } from '../types/TradeRawState' + +export const tradeStateFromUrlAtom = atom(null)