diff --git a/infinite/index.ts b/infinite/index.ts index 56297fc95..bd44be3fd 100644 --- a/infinite/index.ts +++ b/infinite/index.ts @@ -258,14 +258,17 @@ export const infinite = ((useSWRNext: SWRHook) => size: resolvePageSize(), setSize, mutate, - get error() { - return swr.error - }, get data() { return swr.data }, + get error() { + return swr.error + }, get isValidating() { return swr.isValidating + }, + get isFallback() { + return swr.isFallback } } as SWRInfiniteResponse }) as unknown as Middleware diff --git a/src/types.ts b/src/types.ts index 144ab9c33..ccaad377d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -216,10 +216,11 @@ export type SWRConfiguration< > = Partial> export interface SWRResponse { - data?: Data - error?: Error + data: Data | undefined + error: Error | undefined mutate: KeyedMutator isValidating: boolean + isFallback: boolean } export type KeyLoader = diff --git a/src/use-swr.ts b/src/use-swr.ts index 26404dfe5..f8e816da3 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -307,8 +307,11 @@ export const useSWRHandler = ( } catch (err) { cleanupState() + const currentConfig = getConfig() + const { shouldRetryOnError } = currentConfig + // Not paused, we continue handling the error. Otherwise discard it. - if (!getConfig().isPaused()) { + if (!currentConfig.isPaused()) { // Get a new error, don't use deep comparison for errors. set({ error: err }) newState.error = err as Error @@ -316,21 +319,26 @@ export const useSWRHandler = ( // Error event and retry logic. Only for the actual request, not // deduped ones. if (shouldStartNewRequest && isCurrentKeyMounted()) { - getConfig().onError(err, key, config) + currentConfig.onError(err, key, currentConfig) if ( - (typeof config.shouldRetryOnError === 'boolean' && - config.shouldRetryOnError) || - (isFunction(config.shouldRetryOnError) && - config.shouldRetryOnError(err as Error)) + shouldRetryOnError === true || + (isFunction(shouldRetryOnError) && + shouldRetryOnError(err as Error)) ) { - // When retrying, dedupe is always enabled if (isActive()) { - // If it's active, stop. It will auto revalidate when refocusing - // or reconnecting. - getConfig().onErrorRetry(err, key, config, revalidate, { - retryCount: (opts.retryCount || 0) + 1, - dedupe: true - }) + // If it's inactive, stop. It will auto revalidate when + // refocusing or reconnecting. + // When retrying, deduplication is always enabled. + currentConfig.onErrorRetry( + err, + key, + currentConfig, + revalidate, + { + retryCount: (opts.retryCount || 0) + 1, + dedupe: true + } + ) } } } @@ -546,6 +554,12 @@ export const useSWRHandler = ( get isValidating() { stateDependencies.isValidating = true return isValidating + }, + get isFallback() { + stateDependencies.data = true + // `isFallback` is only true when we are displaying a value other than + // the cached one. + return data !== cachedData } } as SWRResponse } diff --git a/test/use-swr-cache.test.tsx b/test/use-swr-cache.test.tsx index b3965eb40..b50851989 100644 --- a/test/use-swr-cache.test.tsx +++ b/test/use-swr-cache.test.tsx @@ -198,19 +198,23 @@ describe('useSWR - cache provider', () => { it('should support fallback values with custom provider', async () => { const key = createKey() function Page() { - const { data } = useSWR(key, async () => { + const { data, isFallback } = useSWR(key, async () => { await sleep(10) return 'data' }) - return <>{String(data)} + return ( + <> + {String(data)},{String(isFallback)} + + ) } renderWithConfig(, { provider: () => provider, fallback: { [key]: 'fallback' } }) - screen.getByText('fallback') // no `undefined`, directly fallback - await screen.findByText('data') + screen.getByText('fallback,true') // no `undefined`, directly fallback + await screen.findByText('data,false') }) it('should not return the fallback if cached', async () => { diff --git a/test/use-swr-loading.test.tsx b/test/use-swr-loading.test.tsx index 8d47bfa98..625195a74 100644 --- a/test/use-swr-loading.test.tsx +++ b/test/use-swr-loading.test.tsx @@ -138,7 +138,7 @@ describe('useSWR - loading', () => { } renderWithConfig() - screen.getByText('data,error,isValidating,mutate') + screen.getByText('data,error,isFallback,isValidating,mutate') }) it('should sync loading states', async () => {