Skip to content

raf-query is early. The API can still change between minor releases, so use it at your own risk.

raf-query

Guides

Window focus refetching

Refetch stale queries on screen when the user comes back to the tab or reconnects, with the focusRefetch() and reconnectRefetch() features, and opt queries out in meta.

When the user leaves your app and comes back, the data on screen may be out of date. Two features refetch it:

  • focusRefetch() refetches every read, stale query when the window regains focus.
  • reconnectRefetch() does the same when the browser comes back online.

Neither is on by default. Add them to the client, or spread recommended(), which has both:

const 
const queryClient: QueryClient<{
    readonly meta: Flat<Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta>>;
}>
queryClient
=
createQueryClient<[], readonly [Feature<{
    readonly meta: FocusRefetchMeta;
}>, Feature<{
    readonly meta: ReconnectRefetchMeta;
}>]>(config?: QueryClientConfig & {
    readonly features?: readonly [Feature<{
        readonly meta: FocusRefetchMeta;
    }>, Feature<{
        readonly meta: ReconnectRefetchMeta;
    }>] | undefined;
    readonly links?: [] | undefined;
}): QueryClient<{
    readonly meta: Flat<Present<FocusRefetchMeta> & Present<...>>;
}>

Create a query client. Behaviors are opt-in: pass links and features, or spread recommended(). The client's meta type is what they declare.

createQueryClient
({
features?: (readonly AnyFeature[] & readonly [Feature<{
    readonly meta: FocusRefetchMeta;
}>, Feature<{
    readonly meta: ReconnectRefetchMeta;
}>]) | undefined

Behaviors started while a provider has the client mounted.

features
: [
function focusRefetch(): Feature<{
    readonly meta: FocusRefetchMeta;
}>

Refetch read, stale queries when the window regains focus, except those with meta: { refetchOnWindowFocus: false }.

focusRefetch
(),
function reconnectRefetch(): Feature<{
    readonly meta: ReconnectRefetchMeta;
}>

Refetch read, stale queries when the browser comes back online, except those with meta: { refetchOnReconnect: false }.

reconnectRefetch
()],
}); // or const
const withEverything: QueryClient<{
    readonly meta: Flat<Present<FocusRefetchMeta> & Present<ReconnectRefetchMeta> & Present<PollingMeta> & Present<RetryMeta>>;
}>
withEverything
=
createQueryClient<readonly [Link<{
    readonly meta: RetryMeta;
}>], readonly [Feature<{
    readonly meta: FocusRefetchMeta;
}>, Feature<{
    readonly meta: ReconnectRefetchMeta;
}>, Feature<{
    readonly meta: PollingMeta;
}>]>(config?: QueryClientConfig & {
    readonly features?: readonly [Feature<{
        readonly meta: FocusRefetchMeta;
    }>, Feature<{
        readonly meta: ReconnectRefetchMeta;
    }>, Feature<...>] | undefined;
    readonly links?: readonly [...] | undefined;
}): QueryClient<...>

Create a query client. Behaviors are opt-in: pass links and features, or spread recommended(). The client's meta type is what they declare.

createQueryClient
({ ...function recommended(): Recommended

The behaviors most apps want: retries, refetching on focus and reconnect, and polling. Spread it into the config:

recommended
() });

Only queries that a component is reading refetch, and only stale ones: data within its staleTime stays as it is. With the default staleTime of 0, that's everything on screen.

Features run while a QueryClientProvider has the client mounted, so they only ever run in the browser.

Turning it off

For every query, leave the feature out of the client:

// `recommended()` without focusRefetch() and reconnectRefetch()
const 
const withoutFocus: QueryClient<{
    readonly meta: Flat<Present<RetryMeta> & Present<PollingMeta>>;
}>
withoutFocus
=
createQueryClient<readonly [Link<{
    readonly meta: RetryMeta;
}>], readonly [Feature<{
    readonly meta: PollingMeta;
}>]>(config?: QueryClientConfig & {
    readonly features?: readonly [Feature<{
        readonly meta: PollingMeta;
    }>] | undefined;
    readonly links?: readonly [Link<{
        readonly meta: RetryMeta;
    }>] | undefined;
}): QueryClient<{
    readonly meta: Flat<Present<RetryMeta> & Present<PollingMeta>>;
}>

Create a query client. Behaviors are opt-in: pass links and features, or spread recommended(). The client's meta type is what they declare.

createQueryClient
({
links?: (readonly AnyLink[] & readonly [Link<{
    readonly meta: RetryMeta;
}>]) | undefined

Wrap every request, outermost first.

links
: [
function retry(options?: RetryOptions): Link<{
    readonly meta: RetryMeta;
}>

Retry failed requests. Thrown Responses (router redirects and not-founds) and cancelled requests aren't retried, server clients never retry, and a browser that is offline waits until it's back online before retrying. A query sets its own attempts with meta: { retry }.

retry
()],
features?: (readonly AnyFeature[] & readonly [Feature<{
    readonly meta: PollingMeta;
}>]) | undefined

Behaviors started while a provider has the client mounted.

features
: [
function polling(): Feature<{
    readonly meta: PollingMeta;
}>

Refetch read queries every meta.refetchInterval ms.

polling
()],
});

For one query, set meta: { refetchOnWindowFocus: false } or meta: { refetchOnReconnect: false } in its factory. The features declare these fields, so a factory built with a bound queryOptions gets them typed only when the root has the feature: see Query meta.

export function 
function settingsQuery(): QueryOptions<readonly ["account", "settings"], {
    readonly theme: string;
}, {
    readonly theme: string;
}, DefaultTypes>
settingsQuery
() {
return
queryOptions<readonly ["account", "settings"], {
    readonly theme: string;
}, {
    readonly theme: string;
}>(options: BaseOptions<readonly ["account", "settings"], DefaultTypes> & {
    readonly queryFn: (context: QueryFunctionContext<readonly ["account", "settings"]>) => Awaitable<{
        readonly theme: string;
    }>;
    readonly select?: ((data: {
        readonly theme: string;
    }) => {
        readonly theme: string;
    }) | undefined;
}): QueryOptions<readonly ["account", "settings"], {
    readonly theme: string;
}, {
    readonly theme: string;
}, DefaultTypes> (+1 overload)

Typed options for a query factory:

queryOptions
({
BaseOptions<readonly ["account", "settings"], DefaultTypes>.queryKey: readonly ["account", "settings"]

A plain array, usually built by a group(). Handles keep its literal type.

queryKey
: account<readonly ["settings"]>(rest_0: "settings"): readonly ["account", "settings"]account("settings"),
queryFn: (context: QueryFunctionContext<readonly ["account", "settings"]>) => Awaitable<{
    readonly theme: string;
}>
queryFn
: () =>
function getSettings(): Promise<{
    readonly theme: string;
}>
getSettings
(),
BaseOptions<readonly ["account", "settings"], DefaultTypes>.meta?: NoInfer<Partial<Readonly<Record<string, unknown>>>> | undefined

Settings for the root's links and features, and data for targets: the fields the root's parts declare, such as retry with the retry() link.

meta
: { refetchOnWindowFocus: booleanrefetchOnWindowFocus: false },
}); }

A long staleTime is often the better fix: the data then refetches on focus only once it's actually old.

What counts as focus

focusRefetch() listens for two events:

  • the document's visibilitychange event, when document.visibilityState becomes "visible": switching back to the tab, restoring the window, unlocking the phone;
  • the window's focus event, while the page is visible: clicking back into the browser window from another app.

reconnectRefetch() listens for the window's online event.

To test it by hand, switch tabs and come back. See Testing for firing these events in tests.