diff --git a/apps/cowswap-frontend/src/legacy/state/swap/hooks.test.ts b/apps/cowswap-frontend/src/legacy/state/swap/hooks.test.ts
index 92c91c0590..04ac6376f1 100644
--- a/apps/cowswap-frontend/src/legacy/state/swap/hooks.test.ts
+++ b/apps/cowswap-frontend/src/legacy/state/swap/hooks.test.ts
@@ -1,7 +1,7 @@
import { parse } from 'qs'
import { Field } from './actions'
-import { queryParametersToSwapState } from './hooks'
+import { queryParametersToSwapState } from './utils'
jest.mock('legacy/components/analytics/hooks/useAnalyticsReporter.ts')
diff --git a/apps/cowswap-frontend/src/legacy/state/swap/hooks.tsx b/apps/cowswap-frontend/src/legacy/state/swap/hooks.tsx
index 9ff7ec8da6..835cf5f5c7 100644
--- a/apps/cowswap-frontend/src/legacy/state/swap/hooks.tsx
+++ b/apps/cowswap-frontend/src/legacy/state/swap/hooks.tsx
@@ -4,7 +4,6 @@ import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { t } from '@lingui/macro'
-import { ParsedQs } from 'qs'
import { changeSwapAmountAnalytics, switchTokensAnalytics } from 'legacy/components/analytics'
import { FEE_SIZE_THRESHOLD } from 'legacy/constants'
@@ -34,9 +33,6 @@ import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { formatSymbol } from 'utils/format'
import { Field, setRecipient, switchCurrencies, typeInput } from './actions'
-import { SwapState } from './reducer'
-
-import { TOKEN_SHORTHANDS } from '../../constants/tokens'
export const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = {
'0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f': true, // v2 factory
@@ -44,36 +40,6 @@ export const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = {
'0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D': true, // v2 router 02
}
-export function parseCurrencyFromURLParameter(urlParam: ParsedQs[string]): string {
- if (typeof urlParam === 'string') {
- const valid = isAddress(urlParam)
- if (valid) return valid
- const upper = urlParam.toUpperCase()
- if (upper === 'ETH') return 'ETH'
- if (upper in TOKEN_SHORTHANDS) return upper
- }
- return ''
-}
-
-export function parseTokenAmountURLParameter(urlParam: any): string {
- return typeof urlParam === 'string' && !isNaN(parseFloat(urlParam)) ? urlParam : ''
-}
-
-export function parseIndependentFieldURLParameter(urlParam: any): Field {
- return typeof urlParam === 'string' && urlParam.toLowerCase() === 'output' ? Field.OUTPUT : Field.INPUT
-}
-
-const ENS_NAME_REGEX = /^[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)?$/
-const ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/
-export function validatedRecipient(recipient: any): string | null {
- if (typeof recipient !== 'string') return null
- const address = isAddress(recipient)
- if (address) return address
- if (ENS_NAME_REGEX.test(recipient)) return recipient
- if (ADDRESS_REGEX.test(recipient)) return recipient
- return null
-}
-
export function useSwapState(): AppState['swap'] {
const isProviderNetworkUnsupported = useIsProviderNetworkUnsupported()
@@ -402,42 +368,6 @@ export function useDerivedSwapInfo(): DerivedSwapInfo {
)
}
-export function queryParametersToSwapState(
- parsedQs: ParsedQs,
- defaultInputCurrency = '',
- chainId: number | null
-): SwapState {
- let inputCurrency = parseCurrencyFromURLParameter(parsedQs.inputCurrency)
- let outputCurrency = parseCurrencyFromURLParameter(parsedQs.outputCurrency)
- const typedValue = parseTokenAmountURLParameter(parsedQs.exactAmount)
- const independentField = parseIndependentFieldURLParameter(parsedQs.exactField)
-
- if (inputCurrency === '' && outputCurrency === '' && typedValue === '' && independentField === Field.INPUT) {
- // Defaults to having the wrapped native currency selected
- inputCurrency = defaultInputCurrency // 'ETH' // mod
- } else if (inputCurrency === outputCurrency) {
- // clear output if identical
- outputCurrency = ''
- }
-
- const recipient = validatedRecipient(parsedQs.recipient)
- const recipientAddress = validatedRecipient(parsedQs.recipientAddress)
-
- return {
- chainId: chainId || null,
- [Field.INPUT]: {
- currencyId: inputCurrency === '' ? null : inputCurrency ?? null,
- },
- [Field.OUTPUT]: {
- currencyId: outputCurrency === '' ? null : outputCurrency ?? null,
- },
- typedValue,
- independentField,
- recipient,
- recipientAddress,
- }
-}
-
export function useIsFeeGreaterThanInput({
address,
chainId,
diff --git a/apps/cowswap-frontend/src/legacy/state/swap/reducer.ts b/apps/cowswap-frontend/src/legacy/state/swap/reducer.ts
index 720817c6d6..ae582b0803 100644
--- a/apps/cowswap-frontend/src/legacy/state/swap/reducer.ts
+++ b/apps/cowswap-frontend/src/legacy/state/swap/reducer.ts
@@ -15,11 +15,12 @@ import {
switchCurrencies,
typeInput,
} from 'legacy/state/swap/actions'
-import { queryParametersToSwapState } from 'legacy/state/swap/hooks'
import { getIsNativeToken } from 'utils/getIsNativeToken'
import { getIsWrapOrUnwrap } from 'utils/getIsWrapOrUnwrap'
+import { queryParametersToSwapState } from './utils'
+
export interface SwapState {
// Mod: added chainId
chainId: number | null
diff --git a/apps/cowswap-frontend/src/legacy/state/swap/utils.ts b/apps/cowswap-frontend/src/legacy/state/swap/utils.ts
index 1ea674e7ee..503ecd6f26 100644
--- a/apps/cowswap-frontend/src/legacy/state/swap/utils.ts
+++ b/apps/cowswap-frontend/src/legacy/state/swap/utils.ts
@@ -1,9 +1,13 @@
import { SupportedChainId } from '@cowprotocol/cow-sdk'
-import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
+import { Currency } from '@uniswap/sdk-core'
-import { WRAPPED_NATIVE_CURRENCY } from 'legacy/constants/tokens'
+import { ParsedQs } from 'qs'
-import TradeGp from './TradeGp'
+import { TOKEN_SHORTHANDS, WRAPPED_NATIVE_CURRENCY } from 'legacy/constants/tokens'
+import { isAddress } from 'legacy/utils'
+
+import { Field } from './actions'
+import { SwapState } from './reducer'
export function isWrappingTrade(
sellCurrency: Currency | null | undefined,
@@ -20,44 +24,69 @@ export function isWrappingTrade(
)
}
-export function logTradeDetails(trade: TradeGp | undefined, allowedSlippage: Percent) {
- // don't do anything outside of dev env
- if (!trade || process.env.NODE_ENV !== 'development') return
-
- const exactIn = trade.tradeType === TradeType.EXACT_INPUT
-
- // Log Exact In Trade info
- if (exactIn) {
- console.debug(
- `[SwapMod::[SELL] Trade Constructed]`,
- `
- Type: SELL
- ==========
- Input Amount: ${trade.inputAmount.toExact()}
- Output Amount: ${trade.outputAmount.toExact()}
- ==========
- Fee Amount [as SELL]: ${trade.fee?.feeAsCurrency?.toExact()} ${trade.inputAmount.currency.symbol}
- Fee Amount [as BUY]: ${
- trade.outputAmountWithoutFee && trade.outputAmountWithoutFee.subtract(trade.outputAmount).toExact()
- } ${trade.outputAmount.currency.symbol}
- ==========
- Minimum Received: ${trade.minimumAmountOut(allowedSlippage).toExact()}
- `
- )
- } else {
- // Log Exact Out Trade info
- console.debug(
- `[SwapMod::[BUY] Trade Constructed]`,
- `
- Type: BUY
- =========
- Input Amount [w/FEE]: ${trade.inputAmountWithFee.toExact()}
- Output Amount: ${trade.outputAmount.toExact()}
- =========
- Fee Amount [as SELL]: ${trade.fee?.feeAsCurrency?.toExact()} ${trade.inputAmount.currency.symbol}
- =========
- Maximum Sold: ${trade.fee?.feeAsCurrency && trade.maximumAmountIn(allowedSlippage).toExact()}
- `
- )
+function parseIndependentFieldURLParameter(urlParam: any): Field {
+ return typeof urlParam === 'string' && urlParam.toLowerCase() === 'output' ? Field.OUTPUT : Field.INPUT
+}
+
+export function parseCurrencyFromURLParameter(urlParam: ParsedQs[string]): string {
+ if (typeof urlParam === 'string') {
+ const valid = isAddress(urlParam)
+ if (valid) return valid
+ const upper = urlParam.toUpperCase()
+ if (upper === 'ETH') return 'ETH'
+ if (upper in TOKEN_SHORTHANDS) return upper
+ }
+ return ''
+}
+
+export function parseTokenAmountURLParameter(urlParam: any): string {
+ return typeof urlParam === 'string' && !isNaN(parseFloat(urlParam)) ? urlParam : ''
+}
+
+const ENS_NAME_REGEX = /^[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)?$/
+const ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/
+
+export function validatedRecipient(recipient: any): string | null {
+ if (typeof recipient !== 'string') return null
+ const address = isAddress(recipient)
+ if (address) return address
+ if (ENS_NAME_REGEX.test(recipient)) return recipient
+ if (ADDRESS_REGEX.test(recipient)) return recipient
+ return null
+}
+
+export function queryParametersToSwapState(
+ parsedQs: ParsedQs,
+ defaultInputCurrency = '',
+ chainId: number | null
+): SwapState {
+ let inputCurrency = parseCurrencyFromURLParameter(parsedQs.inputCurrency)
+ let outputCurrency = parseCurrencyFromURLParameter(parsedQs.outputCurrency)
+ const typedValue = parseTokenAmountURLParameter(parsedQs.exactAmount)
+ const independentField = parseIndependentFieldURLParameter(parsedQs.exactField)
+
+ if (inputCurrency === '' && outputCurrency === '' && typedValue === '' && independentField === Field.INPUT) {
+ // Defaults to having the wrapped native currency selected
+ inputCurrency = defaultInputCurrency // 'ETH' // mod
+ } else if (inputCurrency === outputCurrency) {
+ // clear output if identical
+ outputCurrency = ''
+ }
+
+ const recipient = validatedRecipient(parsedQs.recipient)
+ const recipientAddress = validatedRecipient(parsedQs.recipientAddress)
+
+ return {
+ chainId: chainId || null,
+ [Field.INPUT]: {
+ currencyId: inputCurrency === '' ? null : inputCurrency ?? null,
+ },
+ [Field.OUTPUT]: {
+ currencyId: outputCurrency === '' ? null : outputCurrency ?? null,
+ },
+ typedValue,
+ independentField,
+ recipient,
+ recipientAddress,
}
}
diff --git a/apps/cowswap-frontend/src/modules/usdAmount/hooks/useUsdAmount.test.tsx b/apps/cowswap-frontend/src/modules/usdAmount/hooks/useUsdAmount.test.tsx
new file mode 100644
index 0000000000..db0703a3fc
--- /dev/null
+++ b/apps/cowswap-frontend/src/modules/usdAmount/hooks/useUsdAmount.test.tsx
@@ -0,0 +1,52 @@
+import { createStore } from 'jotai/vanilla'
+import { ReactNode } from 'react'
+
+import { CurrencyAmount } from '@uniswap/sdk-core'
+
+import { renderHook } from '@testing-library/react-hooks'
+import { JotaiTestProvider } from 'test-utils'
+
+import { WETH_GNOSIS_CHAIN } from 'legacy/utils/gnosis_chain/constants'
+
+import { useUsdAmount } from './useUsdAmount'
+
+import { usdRawPricesAtom, UsdRawPriceState } from '../state/usdRawPricesAtom'
+
+const WETH_RAW_PRICE_STATE: UsdRawPriceState = {
+ updatedAt: Date.now(),
+ price: 1650,
+ currency: WETH_GNOSIS_CHAIN,
+ isLoading: false,
+}
+
+function getWrapper() {
+ const store = createStore()
+ const initialValues = [[usdRawPricesAtom, { [WETH_GNOSIS_CHAIN.address.toLowerCase()]: WETH_RAW_PRICE_STATE }]]
+
+ return {
+ store,
+ TestComponent: function ({ children }: { children: ReactNode }) {
+ return (
+
+ {children}
+
+ )
+ },
+ }
+}
+
+describe('useUsdAmount', () => {
+ const ONE_WETH = CurrencyAmount.fromRawAmount(WETH_GNOSIS_CHAIN, 1 * 10 ** WETH_GNOSIS_CHAIN.decimals)
+
+ it('USD amount for 1 WETH should be 1650', async () => {
+ const { TestComponent } = getWrapper()
+ const { result } = renderHook(
+ () => {
+ return useUsdAmount(ONE_WETH)
+ },
+ { wrapper: TestComponent }
+ )
+
+ expect(result.current.value?.toExact()).toBe(WETH_RAW_PRICE_STATE.price?.toString())
+ })
+})
diff --git a/apps/cowswap-frontend/src/modules/usdAmount/updaters/UsdPricesUpdater.test.tsx b/apps/cowswap-frontend/src/modules/usdAmount/updaters/UsdPricesUpdater.test.tsx
new file mode 100644
index 0000000000..e299ec6e94
--- /dev/null
+++ b/apps/cowswap-frontend/src/modules/usdAmount/updaters/UsdPricesUpdater.test.tsx
@@ -0,0 +1,162 @@
+import { createStore } from 'jotai/vanilla'
+import { ReactNode } from 'react'
+
+import { SupportedChainId } from '@cowprotocol/cow-sdk'
+import { Token } from '@uniswap/sdk-core'
+
+import { act, render, waitFor } from '@testing-library/react'
+import { SWRConfig } from 'swr'
+import { JotaiTestProvider } from 'test-utils'
+
+import { COW as COWS, USDC_MAINNET } from 'legacy/constants/tokens'
+
+import { UsdPricesUpdater } from './UsdPricesUpdater'
+
+import * as coingeckoApi from '../apis/getCoingeckoUsdPrice'
+import * as cowProtocolApi from '../apis/getCowProtocolUsdPrice'
+import * as services from '../services/fetchCurrencyUsdPrice'
+import { currenciesUsdPriceQueueAtom, UsdRawPrices, usdRawPricesAtom } from '../state/usdRawPricesAtom'
+
+const mockGetCoingeckoUsdPrice = jest.spyOn(coingeckoApi, 'getCoingeckoUsdPrice')
+const mockGetCowProtocolUsdPrice = jest.spyOn(cowProtocolApi, 'getCowProtocolUsdPrice')
+const mockFetchCurrencyUsdPrice = jest.spyOn(services, 'fetchCurrencyUsdPrice')
+
+const USDC = USDC_MAINNET
+const COW = COWS[SupportedChainId.MAINNET]
+
+const usdcAddress = USDC.address.toLowerCase()
+const cowAddress = COW.address.toLowerCase()
+
+const defaultQueue = {
+ [usdcAddress]: USDC,
+ [cowAddress]: COW,
+}
+
+function getWrapper() {
+ const store = createStore()
+ const initialValues = [[currenciesUsdPriceQueueAtom, { ...defaultQueue }]]
+
+ return {
+ store,
+ TestComponent: function ({ children }: { children: ReactNode }) {
+ return (
+ // https://swr.vercel.app/docs/advanced/cache#reset-cache-between-test-cases
+ new Map() }}>
+
+ {children}
+
+
+ )
+ },
+ }
+}
+
+async function performTest(
+ priceMock: ((currency: Token) => Promise) | null = null,
+ waitForResolvesCount = 0
+): Promise {
+ let resolvesCount = 0
+
+ const { TestComponent, store } = getWrapper()
+
+ if (priceMock) {
+ mockFetchCurrencyUsdPrice.mockImplementation((currency: Token) => {
+ return priceMock(currency).finally(() => {
+ resolvesCount++
+ })
+ })
+ }
+
+ act(() => {
+ render(, { wrapper: TestComponent })
+ })
+
+ await waitFor(() => resolvesCount === waitForResolvesCount)
+
+ return store.get(usdRawPricesAtom)
+}
+
+describe('UsdPricesUpdater', () => {
+ afterEach(() => {
+ jest.resetAllMocks()
+ })
+
+ it('Should mark prices with isLoading=true before fetching', async () => {
+ const state = await performTest(() => {
+ // Never resolve price
+ return new Promise(() => void 0)
+ }, 0)
+
+ expect(state[usdcAddress].isLoading).toBe(true)
+ expect(state[cowAddress].isLoading).toBe(true)
+ })
+
+ it('Should reset isLoading and value fields on fetching error', async () => {
+ const state = await performTest(() => {
+ // Always throw error on fetching try
+ return new Promise((resolve, reject) => {
+ reject(new Error('Server error'))
+ })
+ }, 2)
+
+ expect(state[usdcAddress]).toEqual({ isLoading: false, price: null, currency: USDC })
+ expect(state[cowAddress]).toEqual({ isLoading: false, price: null, currency: COW })
+ })
+
+ it('Should set price value and isLoading=false on fetching success', async () => {
+ const usdcPrice = 1.0
+ const cowPrice = 0.5
+
+ const state = await performTest((currency: Token) => {
+ // Always return prices
+ return new Promise((resolve) => {
+ if (currency === USDC) {
+ resolve(usdcPrice)
+ } else if (currency === COW) {
+ resolve(cowPrice)
+ }
+ })
+ }, 2)
+
+ expect(state[usdcAddress]).toEqual({
+ isLoading: false,
+ price: usdcPrice,
+ currency: USDC,
+ updatedAt: expect.any(Number),
+ })
+ expect(state[cowAddress]).toEqual({
+ isLoading: false,
+ price: cowPrice,
+ currency: COW,
+ updatedAt: expect.any(Number),
+ })
+ })
+
+ it('Should use Coingecko API by default', async () => {
+ const price = 3.5
+
+ mockGetCoingeckoUsdPrice.mockImplementation(() => Promise.resolve(price))
+
+ const state = await performTest()
+
+ expect(state[usdcAddress].price).toBe(price)
+ expect(state[cowAddress].price).toBe(price)
+
+ expect(mockGetCoingeckoUsdPrice).toHaveBeenCalledTimes(2)
+ expect(mockGetCowProtocolUsdPrice).toHaveBeenCalledTimes(0)
+ })
+
+ it('Should fallback to CowProtocol API when Coingecko is down', async () => {
+ const price = 7.22
+
+ mockGetCowProtocolUsdPrice.mockImplementation(() => Promise.resolve(price))
+ mockGetCoingeckoUsdPrice.mockImplementation(() => Promise.reject(new Error('Server error')))
+
+ const state = await performTest()
+
+ expect(state[usdcAddress].price).toBe(price)
+ expect(state[cowAddress].price).toBe(price)
+
+ expect(mockGetCowProtocolUsdPrice).toHaveBeenCalledTimes(2)
+ })
+})
diff --git a/apps/cowswap-frontend/src/modules/usdAmount/updaters/UsdPricesUpdater.tsx b/apps/cowswap-frontend/src/modules/usdAmount/updaters/UsdPricesUpdater.tsx
new file mode 100644
index 0000000000..bdab631f7d
--- /dev/null
+++ b/apps/cowswap-frontend/src/modules/usdAmount/updaters/UsdPricesUpdater.tsx
@@ -0,0 +1,109 @@
+import { useAtomValue, useSetAtom } from 'jotai'
+import { useEffect, useMemo } from 'react'
+
+import { SupportedChainId } from '@cowprotocol/cow-sdk'
+import { Token } from '@uniswap/sdk-core'
+
+import ms from 'ms.macro'
+import useSWR, { SWRConfiguration } from 'swr'
+
+import { USDC } from 'legacy/constants/tokens'
+
+import { useWalletInfo } from 'modules/wallet'
+
+import { getCowProtocolNativePrice } from '../apis/getCowProtocolNativePrice'
+import { fetchCurrencyUsdPrice } from '../services/fetchCurrencyUsdPrice'
+import {
+ currenciesUsdPriceQueueAtom,
+ UsdRawPrices,
+ usdRawPricesAtom,
+ UsdRawPriceState,
+ resetUsdPricesAtom,
+ setUsdPricesLoadingAtom,
+} from '../state/usdRawPricesAtom'
+
+const swrOptions: SWRConfiguration = {
+ refreshInterval: ms`30s`,
+ refreshWhenHidden: false,
+ refreshWhenOffline: false,
+ revalidateOnFocus: true,
+}
+
+export function UsdPricesUpdater() {
+ const { chainId } = useWalletInfo()
+ const setUsdPrices = useSetAtom(usdRawPricesAtom)
+ const setUsdPricesLoading = useSetAtom(setUsdPricesLoadingAtom)
+ const resetUsdPrices = useSetAtom(resetUsdPricesAtom)
+ const currenciesUsdPriceQueue = useAtomValue(currenciesUsdPriceQueueAtom)
+
+ const queue = useMemo(() => Object.values(currenciesUsdPriceQueue), [currenciesUsdPriceQueue])
+
+ const swrResponse = useSWR(
+ ['UsdPricesUpdater', queue, chainId],
+ () => {
+ const getUsdcPrice = usdcPriceLoader(chainId)
+
+ setUsdPricesLoading(queue)
+
+ return processQueue(queue, getUsdcPrice).catch((error) => {
+ resetUsdPrices(queue)
+
+ return Promise.reject(error)
+ })
+ },
+ swrOptions
+ )
+
+ useEffect(() => {
+ const { data, isLoading, error } = swrResponse
+
+ if (error) {
+ console.error('Error loading USD prices', error)
+ return
+ }
+
+ if (isLoading || !data) {
+ return
+ }
+
+ setUsdPrices(data)
+ }, [swrResponse, setUsdPrices])
+
+ return null
+}
+
+function usdcPriceLoader(chainId: SupportedChainId): () => Promise {
+ let usdcPricePromise: Promise | null = null
+
+ return () => {
+ // Cache the result to avoid fetching it multiple times
+ if (!usdcPricePromise) {
+ usdcPricePromise = getCowProtocolNativePrice(USDC[chainId])
+ }
+
+ return usdcPricePromise
+ }
+}
+
+async function processQueue(queue: Token[], getUsdcPrice: () => Promise): Promise {
+ const results = await Promise.all(
+ queue.map((currency) => {
+ return fetchCurrencyUsdPrice(currency, getUsdcPrice).then((price) => {
+ if (typeof price !== 'number') {
+ return null
+ }
+
+ const state: UsdRawPriceState = {
+ updatedAt: Date.now(),
+ price,
+ currency,
+ isLoading: false,
+ }
+
+ return { [currency.address.toLowerCase()]: state }
+ })
+ })
+ )
+
+ return results.reduce((acc, result) => ({ ...acc, ...result }), {})
+}
diff --git a/apps/cowswap-frontend/src/test-utils.tsx b/apps/cowswap-frontend/src/test-utils.tsx
index 95266f73c2..38595a99bd 100644
--- a/apps/cowswap-frontend/src/test-utils.tsx
+++ b/apps/cowswap-frontend/src/test-utils.tsx
@@ -1,5 +1,6 @@
import { Provider as JotaiProvider } from 'jotai'
import { useHydrateAtoms } from 'jotai/utils'
+import { createStore } from 'jotai/vanilla'
import { ReactElement, ReactNode, useMemo } from 'react'
import { initializeConnector, Web3ReactHooks, Web3ReactProvider } from '@web3-react/core'
@@ -20,6 +21,8 @@ import { theme } from 'legacy/theme'
import { LanguageProvider } from './i18n'
+type JotaiStore = ReturnType
+
const MockedI18nProvider = ({ children }: any) => {children}
const MockThemeProvider = ({ children }: { children: React.ReactNode }) => {
@@ -75,13 +78,31 @@ export function WithMockedWeb3({ children, location }: { children?: ReactNode; l
)
}
-const HydrateAtoms = ({ initialValues, children }: { initialValues: any[]; children?: ReactNode }) => {
- useHydrateAtoms(initialValues)
+const HydrateAtoms = ({
+ initialValues,
+ children,
+ store,
+}: {
+ store?: JotaiStore
+ initialValues: any[]
+ children?: ReactNode
+}) => {
+ useHydrateAtoms(initialValues, { store })
return <>{children}>
}
-export const JotaiTestProvider = ({ initialValues, children }: { initialValues: any[]; children?: ReactNode }) => (
-
- {children}
+export const JotaiTestProvider = ({
+ initialValues,
+ children,
+ store,
+}: {
+ initialValues: any[]
+ children?: ReactNode
+ store?: JotaiStore
+}) => (
+
+
+ {children}
+
)