Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(widget): widget tokens flickering #4883

Merged
merged 4 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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)

Expand All @@ -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
Expand All @@ -52,7 +54,7 @@ export function useSetupTradeState(): void {
console.error('Network switching error: ', error)
})
},
[switchNetwork]
[switchNetwork],
)

const debouncedSwitchNetworkInWallet = debounce(([targetChainId]: [SupportedChainId]) => {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,13 @@
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'

/**
* 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)
}
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this is a key change to break the loop.
But what the point of creating tradeStateFromUrlAtom? Probably you did it to cache the tradeStateFromUrl value, correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct! I thought at first that could be related, since the hook is included in multiple places causing it to be constantly re-evaluated. Decided to add since it was already done 🤷

return
}

if (!state?.tradeType) {
return
}
EVENT_EMITTER.emit(CowEvents.ON_CHANGE_TRADE_PARAMS, getTradeParamsEventPayload(state.tradeType, state))
}, [state])
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { atom } from 'jotai'

import { TradeRawState } from '../types/TradeRawState'

export const tradeStateFromUrlAtom = atom<TradeRawState | null>(null)
Loading