diff --git a/src/state/internal/createQueryStore.ts b/src/state/internal/createQueryStore.ts index ac02206f1db..19dbb811f65 100644 --- a/src/state/internal/createQueryStore.ts +++ b/src/state/internal/createQueryStore.ts @@ -3,6 +3,7 @@ import { StateCreator, StoreApi, UseBoundStore, create } from 'zustand'; import { subscribeWithSelector } from 'zustand/middleware'; import { IS_DEV } from '@/env'; import { RainbowError, logger } from '@/logger'; +import { time } from '@/utils'; import { RainbowPersistConfig, createRainbowStore, omitStoreMethods } from './createRainbowStore'; import { $, AttachValue, SignalFunction, Unsubscribe, attachValueSubscriptionMap } from './signal'; @@ -353,16 +354,6 @@ const SHOULD_PERSIST_INTERNAL_STATE_MAP: Record = { const ABORT_ERROR = new Error('[createQueryStore: AbortError] Fetch interrupted'); -export const time = { - seconds: (n: number) => n * 1000, - minutes: (n: number) => time.seconds(n * 60), - hours: (n: number) => time.minutes(n * 60), - days: (n: number) => time.hours(n * 24), - weeks: (n: number) => time.days(n * 7), - infinity: Infinity, - zero: 0, -}; - const MIN_STALE_TIME = time.seconds(5); export function createQueryStore< @@ -530,7 +521,7 @@ export function createQueryStore< const baseMethods = { async fetch(params: TParams | undefined, options: FetchOptions | undefined) { - const { enabled, status } = get(); + const { enabled, error, status } = get(); if (!options?.force && !enabled) return null; @@ -563,26 +554,26 @@ export function createQueryStore< if (!activeRefetchTimeout && subscriptionCount > 0 && staleTime !== 0 && staleTime !== Infinity) { scheduleNextFetch(effectiveParams, options); } - if (enableLogs) console.log('[💾 Returning Cached Data 💾] for queryKey: ', currentQueryKey); + if (enableLogs) console.log('[💾 Returning Cached Data 💾] for params:', JSON.stringify(effectiveParams)); if (keepPreviousData && storeQueryKey !== currentQueryKey) set(state => ({ ...state, queryKey: currentQueryKey })); return cacheEntry?.data ?? null; } } if (!skipStoreUpdates) { - set(state => ({ ...state, error: null, status: QueryStatuses.Loading })); + if (error || !isLoading) set(state => ({ ...state, error: null, status: QueryStatuses.Loading })); activeFetch = { key: currentQueryKey }; } const fetchOperation = async () => { try { - if (enableLogs) console.log('[🔄 Fetching 🔄] for queryKey: ', currentQueryKey, ':: params: ', effectiveParams); + if (enableLogs) console.log('[🔄 Fetching 🔄] for params:', JSON.stringify(effectiveParams)); const rawResult = await (abortInterruptedFetches && !skipStoreUpdates ? fetchWithAbortControl(effectiveParams) : fetcher(effectiveParams, null)); const lastFetchedAt = Date.now(); - if (enableLogs) console.log('[✅ Fetch Successful ✅] for queryKey: ', currentQueryKey); + if (enableLogs) console.log('[✅ Fetch Successful ✅] for params:', JSON.stringify(effectiveParams)); let transformedData: TData; try { @@ -594,7 +585,7 @@ export function createQueryStore< } if (skipStoreUpdates) { - if (enableLogs) console.log('[🥷 Successful Parallel Fetch 🥷] for queryKey: ', currentQueryKey); + if (enableLogs) console.log('[🥷 Successful Parallel Fetch 🥷] for params:', JSON.stringify(effectiveParams)); return transformedData; } @@ -610,9 +601,9 @@ export function createQueryStore< if (!setData && !disableCache) { if (enableLogs) console.log( - '[💾 Setting Cache 💾] for queryKey: ', - currentQueryKey, - 'Has previous data? ', + '[💾 Setting Cache 💾] for params:', + JSON.stringify(effectiveParams), + '| Has previous data?:', !!newState.queryCache[currentQueryKey]?.data ); newState.queryCache = { @@ -625,7 +616,7 @@ export function createQueryStore< }, }; } else if (setData) { - if (enableLogs) console.log('[💾 Setting Data 💾] for queryKey: ', currentQueryKey); + if (enableLogs) console.log('[💾 Setting Data 💾] for params:\n', JSON.stringify(effectiveParams)); setData({ data: transformedData, params: effectiveParams, @@ -670,7 +661,7 @@ export function createQueryStore< return transformedData ?? null; } catch (error) { if (error === ABORT_ERROR) { - if (enableLogs) console.log('[❌ Fetch Aborted ❌] for queryKey: ', currentQueryKey); + if (enableLogs) console.log('[❌ Fetch Aborted ❌] for params:', JSON.stringify(effectiveParams)); return null; } @@ -820,7 +811,7 @@ export function createQueryStore< const currentParams = getCurrentResolvedParams(); const currentKey = state.queryKey; if (currentKey !== lastFetchKey || state.isStale()) { - state.fetch(currentParams, undefined); + state.fetch(currentParams); } else { scheduleNextFetch(currentParams, undefined); } @@ -836,18 +827,23 @@ export function createQueryStore< fetchAfterParamCreation = true; return; } - const { enabled, fetch, isStale, subscriptionCount } = get(); - const currentParams = getCurrentResolvedParams(); - const currentQueryKey = getQueryKey(currentParams); + const { enabled, fetch, isStale, queryKey: storeQueryKey, subscriptionCount } = get(); + + if (!enabled || (subscriptionCount !== 1 && !disableAutoRefetching)) return; + + if (subscriptionCount === 1) { + const currentParams = getCurrentResolvedParams(); + const currentQueryKey = getQueryKey(currentParams); - set(state => ({ ...state, queryKey: currentQueryKey })); + if (storeQueryKey !== currentQueryKey) set(state => ({ ...state, queryKey: currentQueryKey })); - if ((subscriptionCount === 1 || disableAutoRefetching) && enabled) { - if (isStale() || !get().queryCache[currentQueryKey]?.lastFetchedAt) { + if (isStale()) { fetch(currentParams); } else { scheduleNextFetch(currentParams, undefined); } + } else if (disableAutoRefetching) { + fetch(); } }; @@ -953,8 +949,8 @@ export function createQueryStore< const unsub = subscribeFn(() => { const newVal = attachVal.value; if (!equal(oldVal, newVal)) { + if (enableLogs) console.log('[🌀 Param Change 🌀] -', k, '- [Old]:', `${oldVal?.toString()},`, '[New]:', newVal?.toString()); oldVal = newVal; - if (enableLogs) console.log('[🌀 Param Change 🌀] Param changed:', k, ':: Old value:', oldVal, ':: New value:', newVal); onParamChange(); } }); diff --git a/src/state/internal/createRainbowStore.ts b/src/state/internal/createRainbowStore.ts index 99be06daa24..4249298687b 100644 --- a/src/state/internal/createRainbowStore.ts +++ b/src/state/internal/createRainbowStore.ts @@ -2,9 +2,9 @@ import { debounce } from 'lodash'; import { MMKV } from 'react-native-mmkv'; import { StateCreator, create } from 'zustand'; import { PersistOptions, PersistStorage, StorageValue, persist, subscribeWithSelector } from 'zustand/middleware'; +import { IS_IOS } from '@/env'; import { RainbowError, logger } from '@/logger'; - -const PERSIST_RATE_LIMIT_MS = 3000; +import { time } from '@/utils'; const rainbowStorage = new MMKV({ id: 'rainbow-storage' }); @@ -40,7 +40,7 @@ export interface RainbowPersistConfig> { serializer?: (state: StorageValue['state'], version: StorageValue['version']) => string; /** * The throttle rate for the persist operation in milliseconds. - * @default time.seconds(3) + * @default iOS: time.seconds(3) | Android: time.seconds(5) */ persistThrottleMs?: number; /** @@ -107,6 +107,8 @@ interface LazyPersistParams> { value: StorageValue | StorageValue; } +const DEFAULT_PERSIST_THROTTLE_MS = IS_IOS ? time.seconds(3) : time.seconds(5); + /** * Creates a persist storage object for the Rainbow store. * @param config - The configuration options for the persistable Rainbow store. @@ -117,7 +119,7 @@ function createPersistStorage>(config: Rain const { deserializer = serializedState => defaultDeserializeState(serializedState, enableMapSetHandling), serializer = (state, version) => defaultSerializeState(state, version, enableMapSetHandling), - persistThrottleMs = PERSIST_RATE_LIMIT_MS, + persistThrottleMs = DEFAULT_PERSIST_THROTTLE_MS, storageKey, version = 0, } = config; diff --git a/src/state/internal/tests/QueryStoreTest.tsx b/src/state/internal/tests/QueryStoreTest.tsx index 39d231bd112..ca938d21c7b 100644 --- a/src/state/internal/tests/QueryStoreTest.tsx +++ b/src/state/internal/tests/QueryStoreTest.tsx @@ -13,7 +13,8 @@ // import { ParsedAssetsDictByChain } from '@/__swaps__/types/assets'; // import { AddressAssetsReceivedMessage } from '@/__swaps__/types/refraction'; // import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks'; -// import { createQueryStore, time } from '../createQueryStore'; +// import { time } from '@/utils'; +// import { createQueryStore } from '../createQueryStore'; // import { createRainbowStore } from '../createRainbowStore'; // const ENABLE_LOGS = false; diff --git a/src/utils/index.ts b/src/utils/index.ts index aad92e7e346..2d9d56d92c6 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -36,3 +36,4 @@ export { default as withSpeed } from './withSpeed'; export { default as FallbackIcon } from './CoinIcons/FallbackIcon'; export { default as getExchangeIconUrl } from './getExchangeIconUrl'; export { resolveFirstRejectLast } from './resolveFirstRejectLast'; +export { time } from './time'; diff --git a/src/utils/time.ts b/src/utils/time.ts new file mode 100644 index 00000000000..cbf59b0e2fd --- /dev/null +++ b/src/utils/time.ts @@ -0,0 +1,40 @@ +type TimeInMs = number; +type TimeUtils = { + /** Convert seconds to milliseconds */ + seconds: (seconds: number) => TimeInMs; + /** Convert minutes to milliseconds */ + minutes: (minutes: number) => TimeInMs; + /** Convert hours to milliseconds */ + hours: (hours: number) => TimeInMs; + /** Convert days to milliseconds */ + days: (days: number) => TimeInMs; + /** Convert weeks to milliseconds */ + weeks: (weeks: number) => TimeInMs; + /** Represents infinite time */ + infinity: typeof Infinity; + /** Represents zero time */ + zero: 0; +}; + +/** + * Utility object for time conversions and helpers. + * All methods convert the input unit to milliseconds. + * @example + * time.seconds(5) // 5 seconds + * time.minutes(2) // 2 minutes + * time.hours(1) // 1 hour + * time.days(5) // 5 days + * time.weeks(2) // 2 weeks + * –– + * time.infinity // Infinity + * time.zero // 0 + */ +export const time: TimeUtils = { + seconds: seconds => seconds * 1000, + minutes: minutes => time.seconds(minutes * 60), + hours: hours => time.minutes(hours * 60), + days: days => time.hours(days * 24), + weeks: weeks => time.days(weeks * 7), + infinity: Infinity, + zero: 0, +};