From b9da0875cd6391c4ff064d07659c8a390efaf783 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Thu, 25 Jun 2026 18:06:17 +0800 Subject: [PATCH] refactor: optimize swr data refresh paths --- src/components/home/clash-mode-card.tsx | 7 +- src/components/home/ip-info-card.tsx | 1 - src/hooks/use-clash.ts | 17 +- src/hooks/use-icon-cache.ts | 1 - src/hooks/use-log-data.ts | 4 +- src/hooks/use-mihomo-ws-subscription.ts | 21 ++- src/hooks/use-profiles.ts | 5 +- src/hooks/use-system-proxy-state.ts | 13 +- src/hooks/use-system-state.ts | 6 +- src/hooks/use-update.ts | 6 +- src/hooks/use-verge.ts | 8 +- src/main.tsx | 4 +- src/pages/_layout/hooks/use-layout-events.ts | 8 +- src/pages/profiles.tsx | 22 ++- src/providers/app-data-provider.tsx | 15 +- src/services/query-client.ts | 177 +++++++++---------- 16 files changed, 153 insertions(+), 162 deletions(-) diff --git a/src/components/home/clash-mode-card.tsx b/src/components/home/clash-mode-card.tsx index f565296a0..6ad6ae397 100644 --- a/src/components/home/clash-mode-card.tsx +++ b/src/components/home/clash-mode-card.tsx @@ -18,7 +18,7 @@ import { } from '@/providers/app-data-context' import { patchClashMode } from '@/services/cmds' import { showNotice } from '@/services/notice-service' -import { queryClient } from '@/services/query-client' +import { setCacheData } from '@/services/query-client' import type { TranslationKey } from '@/types/generated/i18n-keys' const CLASH_MODES = ['rule', 'global', 'direct'] as const @@ -114,9 +114,8 @@ export const ClashModeCard = () => { // 成功:写穿主源缓存,使实时 mode 立即反映新值——即使随后的 /configs refetch // 失败(TanStack 会保留旧 data),controllerMode 也不会再压过新值导致闪回。 // 若主源从未成功过(old 为 undefined)则保持不动,改由兜底来源反映。 - queryClient.setQueryData( - ['getClashConfig'], - (old) => (old ? { ...old, mode } : old), + setCacheData(['getClashConfig'], (old) => + old ? { ...old, mode } : old, ) // 刷新主源与兜底源以与后端对齐,待数据落地后再清除乐观状态 await Promise.allSettled([refreshClashConfig(), refetchBackendMode()]) diff --git a/src/components/home/ip-info-card.tsx b/src/components/home/ip-info-card.tsx index 040139912..de601458d 100644 --- a/src/components/home/ip-info-card.tsx +++ b/src/components/home/ip-info-card.tsx @@ -425,7 +425,6 @@ function useIPInfo() { queryKey: [IP_INFO_CACHE_KEY], queryFn: getIpInfo, staleTime: Infinity, - gcTime: 60 * 60 * 1000, refetchOnWindowFocus: false, refetchOnReconnect: false, retry: 1, diff --git a/src/hooks/use-clash.ts b/src/hooks/use-clash.ts index 4cd85db14..027c89628 100644 --- a/src/hooks/use-clash.ts +++ b/src/hooks/use-clash.ts @@ -7,8 +7,12 @@ import { getRuntimeConfig, patchClashConfig, } from '@/services/cmds' -import { useQuery } from '@/services/query-client' -import { queryClient } from '@/services/query-client' +import { + getCacheData, + revalidateQuery, + setCacheData, + useQuery, +} from '@/services/query-client' type MutateClashUpdater = | ((old: IConfigData | undefined) => IConfigData | undefined) @@ -90,9 +94,9 @@ export const useClash = () => { } const next = typeof updater === 'function' - ? updater(queryClient.getQueryData(['getRuntimeConfig'])) + ? updater(getCacheData(['getRuntimeConfig'])) : updater - queryClient.setQueryData(['getRuntimeConfig'], next) + setCacheData(['getRuntimeConfig'], next) if (revalidate !== false) { return refetch() } @@ -130,11 +134,10 @@ export const useClashInfo = () => { await patchClashConfig(patch) mutateInfo() - queryClient.invalidateQueries({ queryKey: ['getClashConfig'] }) + revalidateQuery(['getClashConfig']) }) - const invalidateClashConfig = () => - queryClient.invalidateQueries({ queryKey: ['getClashConfig'] }) + const invalidateClashConfig = () => revalidateQuery(['getClashConfig']) return { clashInfo, diff --git a/src/hooks/use-icon-cache.ts b/src/hooks/use-icon-cache.ts index f5ecbfd6e..49a93a7ce 100644 --- a/src/hooks/use-icon-cache.ts +++ b/src/hooks/use-icon-cache.ts @@ -42,7 +42,6 @@ export const useIconCache = ({ refetchOnWindowFocus: false, refetchOnReconnect: false, staleTime: Infinity, - gcTime: 30 * 60 * 1000, retry: 2, }) diff --git a/src/hooks/use-log-data.ts b/src/hooks/use-log-data.ts index f77bae820..5a03266b1 100644 --- a/src/hooks/use-log-data.ts +++ b/src/hooks/use-log-data.ts @@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react' import { MihomoWebSocket, type LogLevel } from 'tauri-plugin-mihomo-api' import { getClashLogs } from '@/services/cmds' -import { queryClient } from '@/services/query-client' +import { setCacheData } from '@/services/query-client' import { useClashLog } from './use-clash-log' import { useMihomoWsSubscription } from './use-mihomo-ws-subscription' @@ -155,7 +155,7 @@ export const useLogData = () => { const refreshGetClashLog = (clear = false) => { if (clear) { if (subscriptionCacheKey) { - queryClient.setQueryData([subscriptionCacheKey], []) + setCacheData([subscriptionCacheKey], []) } } else { hasLoadedInitialLogsRef.current = false diff --git a/src/hooks/use-mihomo-ws-subscription.ts b/src/hooks/use-mihomo-ws-subscription.ts index 8d25421de..392724447 100644 --- a/src/hooks/use-mihomo-ws-subscription.ts +++ b/src/hooks/use-mihomo-ws-subscription.ts @@ -2,7 +2,12 @@ import { useLocalStorage } from 'foxact/use-local-storage' import { type MutableRefObject, useCallback, useEffect, useRef } from 'react' import { type Message, type MihomoWebSocket } from 'tauri-plugin-mihomo-api' -import { queryClient, useQuery } from '@/services/query-client' +import { + getCacheData, + removeCacheData, + setCacheData, + useQuery, +} from '@/services/query-client' export const RECONNECT_DELAY_MS = 1000 @@ -137,7 +142,7 @@ const createSharedSubscriptionEntry = ( /** * Mirrors SWR's MutatorCallback: consumers can pass either a plain value or a * functional updater `(current?: T) => T`. The functional form is resolved - * against the current cache entry before calling `queryClient.setQueryData`. + * against the current cache entry before calling `setCacheData`. */ type NextFn = ( error?: any, @@ -205,7 +210,7 @@ export const useMihomoWsSubscription = ( ): T => { if (typeof data === 'function') { const updater = data as (current?: T) => T | undefined - const current = queryClient.getQueryData([cacheKey]) + const current = getCacheData([cacheKey]) return updater(current) ?? fallbackData } return data ?? fallbackData @@ -215,13 +220,11 @@ export const useMihomoWsSubscription = ( const response = useQuery({ queryKey: responseCacheKey ? [responseCacheKey] : ['$sub$__disabled__'], - queryFn: () => - queryClient.getQueryData([responseCacheKey!]) ?? fallbackData, + queryFn: () => getCacheData([responseCacheKey!]) ?? fallbackData, initialData: () => - queryClient.getQueryData([responseCacheKey ?? '$sub$__disabled__']) ?? + getCacheData([responseCacheKey ?? '$sub$__disabled__']) ?? fallbackData, staleTime: Infinity, - gcTime: 30_000, enabled: subscriptionCacheKey !== null, }) @@ -248,7 +251,7 @@ export const useMihomoWsSubscription = ( } if (data === undefined) return const resolved = resolveNextData(data, subscriptionCacheKey) - queryClient.setQueryData([subscriptionCacheKey], resolved) + setCacheData([subscriptionCacheKey], resolved) } if (throttleMs && throttleMs > 0) { @@ -351,7 +354,7 @@ export const useMihomoWsSubscription = ( const refresh = useCallback(() => { if (subscriptionCacheKey) { - queryClient.removeQueries({ queryKey: [subscriptionCacheKey] }) + removeCacheData([subscriptionCacheKey]) } setDate(Date.now()) }, [subscriptionCacheKey, setDate]) diff --git a/src/hooks/use-profiles.ts b/src/hooks/use-profiles.ts index f40c50351..f35644552 100644 --- a/src/hooks/use-profiles.ts +++ b/src/hooks/use-profiles.ts @@ -6,8 +6,7 @@ import { patchProfile, patchProfilesConfig, } from '@/services/cmds' -import { useQuery } from '@/services/query-client' -import { queryClient } from '@/services/query-client' +import { setCacheData, useQuery } from '@/services/query-client' import { debugLog } from '@/utils/debug' export const useProfiles = () => { @@ -201,7 +200,7 @@ export const useProfiles = () => { await patchProfile(current.uid, { selected: newSelected }) debugLog('[ActivateSelected] 代理选择配置保存成功') - queryClient.setQueryData(['getProxies'], await calcuProxies()) + setCacheData(['getProxies'], await calcuProxies()) } catch (error: unknown) { console.error( '[ActivateSelected] 保存代理选择配置失败:', diff --git a/src/hooks/use-system-proxy-state.ts b/src/hooks/use-system-proxy-state.ts index 4ed56392d..01be4aabe 100644 --- a/src/hooks/use-system-proxy-state.ts +++ b/src/hooks/use-system-proxy-state.ts @@ -4,8 +4,7 @@ import { closeAllConnections } from 'tauri-plugin-mihomo-api' import { useVerge } from '@/hooks/use-verge' import { useClashConfigData, useSystemData } from '@/providers/app-data-context' import { getAutotemProxy } from '@/services/cmds' -import { useQuery } from '@/services/query-client' -import { queryClient } from '@/services/query-client' +import { revalidateQueries, useQuery } from '@/services/query-client' // 系统代理状态检测统一逻辑 export const useSystemProxyState = () => { @@ -65,18 +64,12 @@ export const useSystemProxyState = () => { } } finally { busyRef.current = false - await Promise.all([ - queryClient.invalidateQueries({ queryKey: ['getSystemProxy'] }), - queryClient.invalidateQueries({ queryKey: ['getAutotemProxy'] }), - ]) + await revalidateQueries([['getSystemProxy'], ['getAutotemProxy']]) } } const invalidateProxyState = () => - Promise.all([ - queryClient.invalidateQueries({ queryKey: ['getSystemProxy'] }), - queryClient.invalidateQueries({ queryKey: ['getAutotemProxy'] }), - ]) + revalidateQueries([['getSystemProxy'], ['getAutotemProxy']]) return { indicator, diff --git a/src/hooks/use-system-state.ts b/src/hooks/use-system-state.ts index bb4a8c08a..2e7d1a267 100644 --- a/src/hooks/use-system-state.ts +++ b/src/hooks/use-system-state.ts @@ -5,6 +5,7 @@ import { showNotice } from '@/services/notice-service' import { useQuery } from '@/services/query-client' import { useVerge } from './use-verge' +import { useVisibility } from './use-visibility' export interface SystemState { runningMode: 'Sidecar' | 'Service' @@ -27,6 +28,7 @@ const STARTUP_GRACE_MS = 10_000 */ export function useSystemState() { const { verge, patchVerge } = useVerge() + const pageVisible = useVisibility() const disablingTunRef = useRef(false) const [isStartingUp, setIsStartingUp] = useState(true) @@ -49,7 +51,9 @@ export function useSystemState() { ]) return { runningMode, isAdminMode, isServiceOk } as SystemState }, - refetchInterval: isStartingUp ? 2000 : 30000, + refetchInterval: pageVisible ? (isStartingUp ? 2000 : 30000) : false, + refetchOnWindowFocus: true, + refetchOnReconnect: true, }) const isSidecarMode = systemState.runningMode === 'Sidecar' diff --git a/src/hooks/use-update.ts b/src/hooks/use-update.ts index 06327b070..494c513d0 100644 --- a/src/hooks/use-update.ts +++ b/src/hooks/use-update.ts @@ -1,5 +1,4 @@ -import { useQuery } from '@/services/query-client' -import { queryClient } from '@/services/query-client' +import { setCacheData, useQuery } from '@/services/query-client' import { checkUpdateSafe } from '@/services/update' import { useVerge } from './use-verge' @@ -24,7 +23,7 @@ export const readLastCheckTime = (): number | null => { export const updateLastCheckTime = (timestamp?: number): number => { const now = timestamp ?? Date.now() localStorage.setItem(LAST_CHECK_KEY, now.toString()) - queryClient.setQueryData([LAST_CHECK_KEY], now) + setCacheData([LAST_CHECK_KEY], now) return now } @@ -54,6 +53,7 @@ export const useUpdate = (enabled: boolean = true) => { retry: 2, staleTime: 60 * 60 * 1000, refetchInterval: 24 * 60 * 60 * 1000, + refetchIntervalInBackground: false, refetchOnWindowFocus: false, }) diff --git a/src/hooks/use-verge.ts b/src/hooks/use-verge.ts index 1fda52c2a..34dbb3dc7 100644 --- a/src/hooks/use-verge.ts +++ b/src/hooks/use-verge.ts @@ -2,7 +2,7 @@ import { useCallback } from 'react' import { getVergeConfig, patchVergeConfig } from '@/services/cmds' import { getPreloadConfig, setPreloadConfig } from '@/services/preload' -import { queryClient, useQuery } from '@/services/query-client' +import { getCacheData, setCacheData, useQuery } from '@/services/query-client' export const useVerge = () => { const initialVergeConfig = getPreloadConfig() @@ -30,11 +30,11 @@ export const useVerge = () => { return } if (typeof updaterOrData === 'function') { - const prev = queryClient.getQueryData(['getVergeConfig']) + const prev = getCacheData(['getVergeConfig']) const next = updaterOrData(prev) - queryClient.setQueryData(['getVergeConfig'], next) + setCacheData(['getVergeConfig'], next) } else { - queryClient.setQueryData(['getVergeConfig'], updaterOrData) + setCacheData(['getVergeConfig'], updaterOrData) } } diff --git a/src/main.tsx b/src/main.tsx index b99fd5edd..a793010d0 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -18,7 +18,7 @@ import { resolveThemeMode, getPreloadConfig, } from './services/preload' -import { queryClientConfig } from './services/query-client' +import { swrConfig } from './services/query-client' import { LoadingCacheProvider, ThemeModeProvider, @@ -51,7 +51,7 @@ const initializeApp = (initialThemeMode: 'light' | 'dark') => { - + diff --git a/src/pages/_layout/hooks/use-layout-events.ts b/src/pages/_layout/hooks/use-layout-events.ts index b5a143785..c7eacb95c 100644 --- a/src/pages/_layout/hooks/use-layout-events.ts +++ b/src/pages/_layout/hooks/use-layout-events.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react' import { useListen } from '@/hooks/use-listen' -import { queryClient } from '@/services/query-client' +import { revalidateQueries } from '@/services/query-client' export const useLayoutEvents = ( handleNotice: (payload: [string, string]) => void, @@ -12,9 +12,7 @@ export const useLayoutEvents = ( const unlisteners: Array<() => void> = [] let disposed = false const revalidateKeys = (keys: readonly string[]) => { - keys.forEach((key) => { - queryClient.invalidateQueries({ queryKey: [key] }) - }) + void revalidateQueries(keys.map((key) => [key])) } const register = ( @@ -42,7 +40,7 @@ export const useLayoutEvents = ( } register( - addListener('verge://refresh-clash-config', async () => { + addListener('verge://refresh-clash-config', () => { revalidateKeys([ 'getProxies', 'getVersion', diff --git a/src/pages/profiles.tsx b/src/pages/profiles.tsx index 6bcb0b9e9..03b79339a 100644 --- a/src/pages/profiles.tsx +++ b/src/pages/profiles.tsx @@ -64,8 +64,12 @@ import { updateProfile, } from '@/services/cmds' import { showNotice } from '@/services/notice-service' -import { useQuery } from '@/services/query-client' -import { queryClient } from '@/services/query-client' +import { + fetchCacheData, + revalidateQueries, + setCacheData, + useQuery, +} from '@/services/query-client' import { useLoadingCache, useSetLoadingCache, @@ -233,7 +237,7 @@ const ProfilePage = () => { return () => { unsubscribe.then((cleanup) => cleanup()) } - }, [addListener, mutateProfiles, t]) + }, [addListener, mutateProfiles]) // 添加紧急恢复功能 const onEmergencyRefresh = useLockFn(async () => { @@ -241,10 +245,7 @@ const ProfilePage = () => { try { // 只失效 profiles 相关 query,不影响 WS 订阅、IP 缓存等其他 query - await Promise.all([ - queryClient.invalidateQueries({ queryKey: ['getProfiles'] }), - queryClient.invalidateQueries({ queryKey: ['getRuntimeLogs'] }), - ]) + await revalidateQueries([['getProfiles'], ['getRuntimeLogs']]) // 强制重新获取配置数据 await mutateProfiles() @@ -371,10 +372,7 @@ const ProfilePage = () => { console.warn(`[导入刷新] 常规刷新失败,尝试清除缓存重新获取`) try { // 清除缓存并重新获取 - await queryClient.fetchQuery({ - queryKey: ['getProfiles'], - queryFn: getProfiles, - }) + await fetchCacheData(['getProfiles'], getProfiles) await onEnhance(false) showNotice.error( 'profiles.page.feedback.notifications.importNeedsRefresh', @@ -482,7 +480,7 @@ const ProfilePage = () => { } } } - queryClient.setQueryData(['getProxies'], await calcuProxies()) + setCacheData(['getProxies'], await calcuProxies()) // 完成切换 await mutateLogs() diff --git a/src/providers/app-data-provider.tsx b/src/providers/app-data-provider.tsx index 66f6cd729..3df291a93 100644 --- a/src/providers/app-data-provider.tsx +++ b/src/providers/app-data-provider.tsx @@ -14,8 +14,7 @@ import { getRunningMode, getSystemProxy, } from '@/services/cmds' -import { useQuery } from '@/services/query-client' -import { queryClient } from '@/services/query-client' +import { revalidateQueries, useQuery } from '@/services/query-client' import { ClashConfigContext, @@ -138,11 +137,13 @@ export const AppDataProvider = ({ } lastProfileId = newProfileId lastUpdateTime = now - void queryClient.invalidateQueries({ queryKey: ['getProfiles'] }) - refreshProxy().catch(() => {}) - refreshProxyProviders().catch(() => {}) - refreshRules().catch(() => {}) - refreshRuleProviders().catch(() => {}) + void Promise.allSettled([ + revalidateQueries([['getProfiles']]), + refreshProxy(), + refreshProxyProviders(), + refreshRules(), + refreshRuleProviders(), + ]) } const handleRefreshProxy = () => { diff --git a/src/services/query-client.ts b/src/services/query-client.ts index d3b613741..8b72bdf5a 100644 --- a/src/services/query-client.ts +++ b/src/services/query-client.ts @@ -14,7 +14,6 @@ type QueryOptions = { initialData?: T | (() => T | undefined) placeholderData?: T | (() => T | undefined) staleTime?: number - gcTime?: number retry?: number | false retryDelay?: number | ((attempt: number) => number) refetchInterval?: number | false @@ -23,106 +22,82 @@ type QueryOptions = { refetchOnReconnect?: boolean } -type QueryResult = Omit, 'mutate'> & { +type QueryResult = SWRResponse & { isFetching: boolean isPending: boolean refetch: () => Promise<{ data: T | undefined }> - mutate: SWRResponse['mutate'] } -type QueryFilters = { - queryKey: QueryKey -} - -const keyToSWRKey = (queryKey: QueryKey) => { - return Array.isArray(queryKey) ? [...queryKey] : queryKey -} - -const serializeQueryKey = (queryKey: QueryKey) => { - return unstable_serialize(keyToSWRKey(queryKey)) -} - -const resolveFallbackData = ( - initialData: QueryOptions['initialData'], - placeholderData: QueryOptions['placeholderData'], -) => { - const data = initialData ?? placeholderData - return typeof data === 'function' ? (data as () => T | undefined)() : data -} - -const resolveRetryInterval = ( - retryDelay: QueryOptions['retryDelay'], -) => { - if (typeof retryDelay !== 'function') return retryDelay - return retryDelay(0) -} +const serializeQueryKey = (queryKey: QueryKey) => unstable_serialize(queryKey) export const queryCache = new Map() -export const queryClientConfig: SWRConfiguration = { +const setCachedData = (queryKey: QueryKey, data: T | undefined) => { + const cacheKey = serializeQueryKey(queryKey) + if (data === undefined) { + queryCache.delete(cacheKey) + } else { + queryCache.set(cacheKey, data) + } +} + +export const swrConfig: SWRConfiguration = { dedupingInterval: 2000, errorRetryCount: 3, errorRetryInterval: 5000, revalidateOnFocus: false, } -export const queryClient = { - getQueryData(queryKey: QueryKey): T | undefined { - return queryCache.get(serializeQueryKey(queryKey)) as T | undefined - }, - - setQueryData( - queryKey: QueryKey, - updaterOrData: T | undefined | ((current: T | undefined) => T | undefined), - ) { - const current = this.getQueryData(queryKey) - const next = - typeof updaterOrData === 'function' - ? (updaterOrData as (current: T | undefined) => T | undefined)(current) - : updaterOrData - - if (next === undefined) { - queryCache.delete(serializeQueryKey(queryKey)) - } else { - queryCache.set(serializeQueryKey(queryKey), next) - } - - void swrMutate(keyToSWRKey(queryKey), next, { - populateCache: true, - revalidate: false, - }) - return next - }, - - invalidateQueries({ queryKey }: QueryFilters) { - return swrMutate(keyToSWRKey(queryKey)) - }, - - removeQueries({ queryKey }: QueryFilters) { - queryCache.delete(serializeQueryKey(queryKey)) - return swrMutate(keyToSWRKey(queryKey), undefined, { - populateCache: true, - revalidate: false, - }) - }, - - async fetchQuery({ - queryKey, - queryFn, - }: { - queryKey: QueryKey - queryFn: () => Promise | T - }) { - const data = await queryFn() - this.setQueryData(queryKey, data) - return data - }, +export const getCacheData = (queryKey: QueryKey): T | undefined => { + return queryCache.get(serializeQueryKey(queryKey)) as T | undefined } -export const invalidateQueries = (queryKeys: readonly QueryKey[]) => - Promise.all( - queryKeys.map((queryKey) => queryClient.invalidateQueries({ queryKey })), - ) +export const setCacheData = ( + queryKey: QueryKey, + updaterOrData: T | undefined | ((current: T | undefined) => T | undefined), +) => { + const current = getCacheData(queryKey) + const next = + typeof updaterOrData === 'function' + ? (updaterOrData as (current: T | undefined) => T | undefined)(current) + : updaterOrData + + setCachedData(queryKey, next) + + void swrMutate(queryKey, next, { + populateCache: true, + revalidate: false, + }) + return next +} + +export const revalidateQuery = async (queryKey: QueryKey) => { + const data = await swrMutate(queryKey) + if (data !== undefined) { + setCachedData(queryKey, data) + } + return data +} + +export const revalidateQueries = (queryKeys: readonly QueryKey[]) => + Promise.all(queryKeys.map(revalidateQuery)) + +export const removeCacheData = (queryKey: QueryKey) => { + setCachedData(queryKey, undefined) + return swrMutate(queryKey, undefined, { + populateCache: true, + revalidate: false, + }) +} + +export const fetchCacheData = async ( + queryKey: QueryKey, + queryFn: () => Promise | T, +) => { + const data = await queryFn() + setCacheData(queryKey, data) + return data +} export function useQuery(options: QueryOptions): QueryResult { const { @@ -140,24 +115,44 @@ export function useQuery(options: QueryOptions): QueryResult { staleTime, } = options - const fallbackData = resolveFallbackData(initialData, placeholderData) + const fallbackDataSource = initialData ?? placeholderData + const fallbackData = + typeof fallbackDataSource === 'function' + ? (fallbackDataSource as () => T | undefined)() + : fallbackDataSource const serializedKey = serializeQueryKey(queryKey) if (enabled && fallbackData !== undefined && !queryCache.has(serializedKey)) { - queryCache.set(serializedKey, fallbackData) + setCachedData(queryKey, fallbackData) } - const swr = useSWR(enabled ? keyToSWRKey(queryKey) : null, queryFn, { + const swr = useSWR(enabled ? queryKey : null, queryFn, { dedupingInterval: staleTime, errorRetryCount: retry === false ? 0 : retry, - errorRetryInterval: resolveRetryInterval(retryDelay), + errorRetryInterval: + typeof retryDelay === 'number' + ? retryDelay + : swrConfig.errorRetryInterval, fallbackData, keepPreviousData: placeholderData !== undefined, + onErrorRetry: (_error, _key, config, revalidate, { retryCount }) => { + const maxRetries = config.errorRetryCount + if (maxRetries !== undefined && retryCount > maxRetries) return + + const interval = + typeof retryDelay === 'function' + ? retryDelay(Math.max(retryCount - 1, 0)) + : config.errorRetryInterval + + setTimeout(() => { + revalidate({ retryCount, dedupe: true }) + }, interval) + }, revalidateOnFocus: refetchOnWindowFocus, revalidateOnReconnect: refetchOnReconnect, refreshInterval: refetchInterval || 0, refreshWhenHidden: refetchIntervalInBackground ?? false, onSuccess: (data) => { - queryCache.set(serializedKey, data) + setCachedData(queryKey, data) }, }) @@ -168,7 +163,7 @@ export function useQuery(options: QueryOptions): QueryResult { refetch: async () => { const data = await swr.mutate() if (data !== undefined) { - queryCache.set(serializedKey, data) + setCachedData(queryKey, data) } return { data } },