Skip to content

Commit

Permalink
fix(widget): widget tokens flickering (#4883)
Browse files Browse the repository at this point in the history
* fix(widget): never notify widget consumer on first load

* refactor: move tradeStateFromUrl to atom, to update it in a single place

* chore: remove debug logs

* chore: fix linting
  • Loading branch information
alfetopito authored Sep 16, 2024
1 parent e28364f commit 0f8f256
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 33 deletions.
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
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)

0 comments on commit 0f8f256

Please sign in to comment.