Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/_internal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ export interface PublicConfiguration<
* @defaultValue false
*/
refreshWhenOffline?: boolean
/**
* polling when the window is not focused (if `refreshInterval` is enabled)
* @defaultValue true
*/
refreshWhenUnfocused?: boolean
/**
* automatically revalidate when window gets focused
*
Expand Down Expand Up @@ -317,6 +322,11 @@ export interface PublicConfiguration<
* @see {@link https://swr.vercel.app/docs/advanced/react-native#customize-focus-and-reconnect-events}
*/
isVisible: () => boolean
/**
* hasFocus is a function that returns a boolean, to determine if the window has focus. Used for controlling polling behavior via refreshWhenUnfocused.
* @see {@link https://swr.vercel.app/docs/advanced/react-native#customize-focus-and-reconnect-events}
*/
hasFocus: () => boolean
}

export type FullConfiguration<
Expand Down
1 change: 1 addition & 0 deletions src/_internal/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const defaultConfig: FullConfiguration = mergeObjects(
revalidateOnReconnect: true,
revalidateIfStale: true,
shouldRetryOnError: true,
refreshWhenUnfocused: true,

// timeouts
errorRetryInterval: slowConnection ? 10000 : 5000,
Expand Down
13 changes: 12 additions & 1 deletion src/_internal/utils/web-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ const isVisible = () => {
return isUndefined(visibilityState) || visibilityState !== 'hidden'
}

const hasFocus = () => {
if (!isDocumentDefined) return true

try {
return typeof document.hasFocus === 'function' ? document.hasFocus() : true
} catch (err) {
return true
}
}

const initFocus = (callback: () => void) => {
// focus revalidate
if (isDocumentDefined) {
Expand Down Expand Up @@ -60,7 +70,8 @@ const initReconnect = (callback: () => void) => {

export const preset = {
isOnline,
isVisible
isVisible,
hasFocus
} as const

export const defaultConfigOptions: ProviderConfiguration = {
Expand Down
16 changes: 12 additions & 4 deletions src/index/use-swr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
refreshInterval,
refreshWhenHidden,
refreshWhenOffline,
refreshWhenUnfocused,
keepPreviousData,
strictServerPrefetchWarning
} = config
Expand Down Expand Up @@ -299,7 +300,7 @@

const data = isUndefined(cachedData)
? fallback && isPromiseLike(fallback)
? use(fallback)

Check failure on line 303 in src/index/use-swr.ts

View workflow job for this annotation

GitHub Actions / test

This expression is not callable.

Check failure on line 303 in src/index/use-swr.ts

View workflow job for this annotation

GitHub Actions / test

This expression is not callable.
: fallback
: cachedData
const error = cached.error
Expand Down Expand Up @@ -670,7 +671,7 @@
if (
getConfig().revalidateOnFocus &&
now > nextFocusRevalidatedAt &&
isActive()
getConfig().isOnline()
) {
nextFocusRevalidatedAt = now + getConfig().focusThrottleInterval
softRevalidate()
Expand Down Expand Up @@ -742,11 +743,12 @@

function execute() {
// Check if it's OK to execute:
// Only revalidate when the page is visible, online, and not errored.
// Only revalidate when the page is visible, online, focused, and not errored.
if (
!getCache().error &&
(refreshWhenHidden || getConfig().isVisible()) &&
(refreshWhenOffline || getConfig().isOnline())
(refreshWhenOffline || getConfig().isOnline()) &&
(refreshWhenUnfocused || getConfig().hasFocus())
) {
revalidate(WITH_DEDUPE).then(next)
} else {
Expand All @@ -763,7 +765,13 @@
timer = -1
}
}
}, [refreshInterval, refreshWhenHidden, refreshWhenOffline, key])
}, [
refreshInterval,
refreshWhenHidden,
refreshWhenOffline,
refreshWhenUnfocused,
key
])

// Display debug info in React DevTools.
useDebugValue(returnedData)
Expand Down Expand Up @@ -791,7 +799,7 @@

const mutateReq =
!isUndefined(req) && hasKeyButNoData ? boundMutate(req) : resolvedUndef
use(mutateReq)

Check failure on line 802 in src/index/use-swr.ts

View workflow job for this annotation

GitHub Actions / test

This expression is not callable.

Check failure on line 802 in src/index/use-swr.ts

View workflow job for this annotation

GitHub Actions / test

This expression is not callable.

if (!isUndefined(error) && hasKeyButNoData) {
throw error
Expand All @@ -805,7 +813,7 @@
// @ts-ignore modify react promise value
revalidation.value = true
}
use(revalidation)

Check failure on line 816 in src/index/use-swr.ts

View workflow job for this annotation

GitHub Actions / test

This expression is not callable.

Check failure on line 816 in src/index/use-swr.ts

View workflow job for this annotation

GitHub Actions / test

This expression is not callable.
}

const swrResponse: SWRResponse<Data, Error> = {
Expand Down
32 changes: 32 additions & 0 deletions test/unit/web-preset-visible.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { preset } from '../../src/_internal/utils/web-preset'

describe('web-preset isVisible', () => {
it('returns a boolean', () => {
expect(typeof preset.isVisible()).toBe('boolean')
})

it('checks document.visibilityState', () => {
// In a real browser, this would return true when visible
// In jsdom, it returns true by default
const result = preset.isVisible()
expect(typeof result).toBe('boolean')
})
})

describe('web-preset hasFocus', () => {
it('returns a boolean', () => {
expect(typeof preset.hasFocus()).toBe('boolean')
})

it('checks document.hasFocus()', () => {
// In jsdom, hasFocus() should be available on document
const result = preset.hasFocus()
expect(typeof result).toBe('boolean')
})

it('handles missing hasFocus gracefully', () => {
// hasFocus should return a boolean
const result = preset.hasFocus()
expect(typeof result).toBe('boolean')
})
})
Loading