Refetch on Window Focus and Reconnect

TanStack Query refetches stale data when the user returns to your tab or the network comes back — the mechanism behind the “always fresh when I look at it” feel of well-built apps. But the same mechanism, left unbounded, fires a burst of requests every time a user alt-tabs, which hammers your API and can produce visible loading flashes. This page is a concrete recipe within Background Refetch Strategies, and it explains exactly how refetchOnWindowFocus, refetchOnReconnect, the focusManager, and staleTime interact. For the polling-interval side of background revalidation, the companion page Optimizing SWR Revalidation Intervals tunes the time-based triggers rather than the event-based ones covered here.

The root cause of most focus-refetch complaints is a misunderstanding of the staleTime gate: focus and reconnect events do not force a refetch, they request one, and the request is only honored if the query is already stale.

Diagnostic Checklist

Before changing config, confirm which symptom you actually have:

  • Every tab switch triggers a network request for a query you expected to stay cached — staleTime is 0, so the query is stale the instant it settles.
  • A query never refetches on focus even though you set refetchOnWindowFocus: truestaleTime is long enough that the query is still fresh.
  • An expensive aggregate query reloads on every focus and causes a spinner flash — it needs a per-query opt-out, not a global one.
  • Refetch on focus does nothing in React Native or an embedded webview — the default focusManager events are not firing.
  • Requests fire on reconnect that you did not expect — refetchOnReconnect defaults to true and is honored for any stale query.

Focus and reconnect refetch decision flow in TanStack Query v5 A focus or online event enters a decision chain: if refetchOnWindowFocus or refetchOnReconnect is disabled the event is ignored, otherwise TanStack Query checks the staleTime gate. A fresh query is served from cache with no request, while a stale query triggers a background refetch. What happens when the window regains focus (or reconnects) focus / online focusManager / onlineManager fires refetchOnWindowFocus / refetchOnReconnect ? false → ignore event no request (disabled) true staleTime gate is the query stale? now > updatedAt+staleTime fresh serve from cache no request stale background refetch request fires
The focus/online event only reaches the network when the refetch option is enabled AND the staleTime gate reports the query as stale.

Step-by-Step Implementation

Step 1 — Understand what triggers a focus and reconnect refetch

The focusManager and onlineManager are singletons that emit events. On the web, focusManager listens for visibilitychange and focus; onlineManager listens for the browser online/offline events. When either fires, TanStack Query walks active queries and evaluates whether each should refetch.

// query-client.ts
import { QueryClient } from '@tanstack/react-query';

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // Both default to true — every stale active query refetches on the event
      refetchOnWindowFocus: true,
      refetchOnReconnect: true,
      // The gate: how long a query is considered fresh after it settles
      staleTime: 30_000,
    },
  },
});

Cache Behavior Analysis. These options do not add event listeners per query — the managers are global and fire once per event. On each event TanStack Query iterates the active observers and, for each, checks the option flag and the staleness of the underlying query. A query with no mounted observer is skipped entirely, which is why a background tab’s unmounted queries do not stampede your API on focus.


Step 2 — Gate focus refetches with staleTime

staleTime is the single most important knob here. A focus event asks each active query to refetch, but TanStack Query only honors the request for queries that are stale. With staleTime: 0 (the default), a query is stale the instant it settles, so every focus refetches. Raising staleTime suppresses focus refetches within the fresh window.

import { useQuery } from '@tanstack/react-query';

function useUserProfile(userId: string) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then((r) => r.json()),
    // Fresh for 5 minutes — focus events inside this window do NOT refetch
    staleTime: 5 * 60_000,
    refetchOnWindowFocus: true, // still honored, but only once the query is stale
  });
}

Cache Behavior Analysis. When the focus event fires, TanStack Query compares dataUpdatedAt + staleTime against the current time. If the data is still fresh, the focus request is treated as already satisfied by cache and no network call is made — the query stays in success state without transitioning to fetching. This is why refetchOnWindowFocus: true and a long staleTime coexist: the flag enables the trigger, staleTime decides whether the trigger does anything.


Step 3 — Configure per-query and disable for expensive queries

Global defaults are the baseline; override per query for expensive endpoints. Set refetchOnWindowFocus: false on a heavy aggregate so returning to the tab does not re-run it, while cheap queries keep the default behavior.

function useRevenueDashboard() {
  return useQuery({
    queryKey: ['revenue', 'dashboard'],
    queryFn: fetchRevenueAggregate,
    // Expensive multi-join query — do not re-run just because the tab regained focus
    refetchOnWindowFocus: false,
    // Still refetch when the network returns, since offline data may be very stale
    refetchOnReconnect: true,
    staleTime: 10 * 60_000,
  });
}

Cache Behavior Analysis. Setting refetchOnWindowFocus: false removes this query from the focus-event walk entirely — the event fires but this observer opts out before the staleTime check. Keeping refetchOnReconnect: true means a genuine connectivity restoration still refetches, which matters because data fetched before going offline can be arbitrarily stale regardless of staleTime. Splitting the two flags lets you suppress the noisy trigger while keeping the meaningful one.


Step 4 — Customize the focusManager for non-browser environments

In React Native or an embedded webview, the browser visibilitychange event does not exist, so focus refetch silently never fires. Subscribe the focusManager to the platform’s own lifecycle so it reports focus correctly.

// focus-manager-native.ts
import { focusManager } from '@tanstack/react-query';
import { AppState, type AppStateStatus } from 'react-native';

focusManager.setEventListener((handleFocus) => {
  const subscription = AppState.addEventListener('change', (status: AppStateStatus) => {
    // Report focused whenever the app is in the foreground
    handleFocus(status === 'active');
  });
  return () => subscription.remove();
});

Cache Behavior Analysis. focusManager.setEventListener replaces the default browser listener wholesale, so the callback you pass becomes the only source of focus signals. Calling handleFocus(true) runs the same active-query walk that a browser focus event would, honoring each query’s refetchOnWindowFocus and staleTime. Because the manager is a singleton, one registration at app startup covers every query in the tree.

  • Treat staleTime as the real throttle for focus refetch; refetchOnWindowFocus only enables or disables the trigger, it does not rate-limit it.
  • Disable refetchOnWindowFocus per query for expensive endpoints instead of globally, so cheap queries keep their freshness benefit.
  • Keep refetchOnReconnect: true even when you disable focus refetch — post-offline data can be stale beyond any staleTime.
  • Register a custom focusManager listener once at startup for React Native or webviews; without it focus refetch is a no-op.

Edge Cases and Gotchas

refetchOnWindowFocus: 'always'

Setting the option to the string 'always' bypasses the staleTime gate entirely — the query refetches on every focus regardless of freshness. This is occasionally correct for a live-trading ticker, but it is a common accidental cause of request storms when applied globally. Reserve 'always' for the handful of queries that truly must ignore staleTime.

Rapid focus/blur flapping

Switching windows quickly can fire multiple focus events in succession. TanStack Query deduplicates concurrent refetches for the same query via its request-in-flight tracking, so a second focus during an in-flight refetch does not launch a second request. You do not need your own debounce for this case; the query cache already collapses duplicate fetches.

Disabled queries and focus

A query with enabled: false is not an active fetching query, so focus and reconnect events never refetch it — even with refetchOnWindowFocus: true. If you gate a query behind enabled and expect focus to wake it, that will not happen; trigger it explicitly with refetch() or flip enabled instead.


Common Pitfalls and Resolutions

Observable Issue Root Cause Diagnostic Resolution
Every tab switch fires a network request staleTime is 0, so the query is stale immediately and every focus is honored Raise staleTime to the acceptable freshness window; the focus trigger stays enabled but is gated
Query never refetches on focus staleTime is long enough that the query is still fresh when the event fires Lower staleTime, or set refetchOnWindowFocus: 'always' for queries that must ignore freshness
Expensive dashboard reloads on every focus with a spinner flash Global refetchOnWindowFocus: true applies to the heavy query Set refetchOnWindowFocus: false on that specific query while keeping refetchOnReconnect: true
Focus refetch does nothing in React Native The default focusManager listens for browser events that do not exist on native Register focusManager.setEventListener wired to AppState at app startup

Frequently Asked Questions

Why does my query still not refetch on focus even with refetchOnWindowFocus true?

refetchOnWindowFocus only refetches queries that are stale. If staleTime has not elapsed since the data last settled, the query is fresh and TanStack Query treats the focus event as already satisfied by cache, so no request fires. Check the query’s staleTime — lower it, or set it to 0, if you want every focus to trigger a network refetch. The flag enables the trigger; staleTime decides whether the trigger reaches the network.

How do I stop an expensive dashboard query from refetching every time the tab regains focus?

Set refetchOnWindowFocus: false on that specific query so it opts out of the focus-event walk, or raise its staleTime so it stays fresh across typical focus intervals. Keeping the default refetchOnWindowFocus for cheap queries while disabling it per-query for the expensive one gives targeted control without a global override. Consider leaving refetchOnReconnect: true on the same query so a genuine reconnection still refreshes data that went stale while offline.

Does refetchOnWindowFocus work in React Native or does it need a custom focusManager?

The default focusManager listens for the browser visibilitychange and focus events, which do not exist in React Native, so focus refetch silently never fires there. Subscribe the focusManager to AppState via focusManager.setEventListener and call handleFocus(status === 'active') on change. After that one registration at startup, refetchOnWindowFocus and the staleTime gate behave exactly as they do on the web.


  • Background Refetch Strategies — the parent reference covering adaptive polling, visibility-driven revalidation, and network-aware fetchers across React Query, SWR, and RTK Query.
  • Optimizing SWR Revalidation Intervals — the time-based counterpart to this event-based page, tuning refreshInterval, dedupingInterval, and focus revalidation in SWR v2.
  • Cache Invalidation & Server Synchronization — the foundational pillar covering invalidation timing, refetch strategies, and the freshness guarantees that focus and reconnect refetch enforce.
  • Mutation Sync & Rollback — how the onSettled invalidation after a mutation interacts with the staleTime gate that governs focus refetch on this page.