skip to content

In a React Native app using TanStack Query v5, what must you add to get refetch on reconnect, and what changes while offline?

level: juniorimportance: should knowfreq 42%

answer

  1. no online or offline window events
  2. onlineManager assumes online forever
  3. setEventListener returns NetInfo's unsubscribe
  4. setOnline(!!state.isConnected)
  5. offline fetches pause instead of failing

basics

~20 s

Wire TanStack Query's onlineManager to NetInfo with onlineManager.setEventListener, calling setOnline from a NetInfo.addEventListener callback. Without it the library believes the phone is always online, so offline requests fail and retry instead of pausing and resuming on reconnect.

solid answer

~40 s

In a browser `onlineManager` listens for the `online` and `offline` events on `window`; React Native has neither, so the manager keeps its initial value, online, forever. Queries then fire into a dead connection, fail, and burn their retries. The fix is a one-time setup: `onlineManager.setEventListener(setOnline => NetInfo.addEventListener(state => setOnline(!!state.isConnected)))`. NetInfo's `addEventListener` returns the unsubscribe function, which is what the setup must return. Once wired, a query that starts while offline stays in its current `status` with `fetchStatus: 'paused'`; a request whose retries fail after the connection drops pauses too. On reconnect, paused fetches continue and observed stale queries refetch because `refetchOnReconnect` defaults to `true` in the default `networkMode`.

code

typescript · 10 lines
typescript
// queryOnline.ts - import once from the app entry
import NetInfo from '@react-native-community/netinfo';
import { onlineManager } from '@tanstack/react-query';

onlineManager.setEventListener((setOnline) => {
  // NetInfo.addEventListener returns its unsubscribe function
  return NetInfo.addEventListener((state) => {
    setOnline(!!state.isConnected);
  });
});

go deeper

for a junior

Recall that a React Native app must wire onlineManager to NetInfo once, and that offline queries then pause instead of failing.

for a middle

Explain status versus fetchStatus, what paused means for a first load, and the difference between a paused fetch continuing and a reconnect refetch.

for a senior

Discuss which NetInfo field to trust, the unknown-null trap at launch, and how the UI should present a paused first load.

for a principal

Decide how much of the app's offline behaviour should rest on one connectivity boolean, and what the team accepts when that signal is wrong.

## The default assumes a browser TanStack Query has a second environment singleton next to `focusManager`: **`onlineManager`**. It holds one boolean, starting at `true`, and its default setup listens for the `online` and `offline` events on `window`. Those are browser events. React Native gives its global `window` no `addEventListener`, so the default registers nothing and the value never leaves `true`. What that means for the investing app on a train going through a tunnel: - a portfolio query that mounts with no connection runs its `queryFn` anyway; - `fetch` fails, and the query retries (three times by default, with growing delays); - the query lands in `status: 'error'`, even though nothing is wrong with the server and the connection may be back seconds later; - when the connection returns, nothing tells TanStack Query, so no reconnect refetch happens. ## The wiring The React Native page of the TanStack Query docs gives the fix, using `@react-native-community/netinfo`: 1. call `onlineManager.setEventListener` once, at app start; 2. inside the setup, subscribe with `NetInfo.addEventListener`, which calls your listener soon after subscribing and on every change; 3. forward `!!state.isConnected` to the `setOnline` callback; 4. return the value `NetInfo.addEventListener` returned: it is a plain unsubscribe function, and `setEventListener` calls it when the listener is replaced. The `!!` matters because NetInfo's connection fields are `boolean | null`, with `null` meaning unknown. Forwarding `null` would not type-check, and coercing it decides what "unknown" means for your queries. ## What changes while offline With the manager fed, TanStack Query separates **status** (do we have data or an error) from **fetchStatus** (is a request running): | Situation | status | fetchStatus | |---|---|---| | query mounts offline, no cached data | `pending` | `paused` | | query with data refetches offline | `success` | `paused` | | online, request in flight | unchanged | `fetching` | | nothing running | unchanged | `idle` | Consequences you design the screen around: - a first load while offline is `pending` **and** `paused`, so a spinner keyed only to `isPending` spins forever; check `isPaused` or `fetchStatus` to show an offline message instead; - a request that was already running when the connection dropped keeps its promise, but its retries **pause** rather than fail; - when `setOnline(true)` arrives, the client resumes paused mutations, then each query **continues** a paused fetch and, separately, observed queries that are stale refetch because `refetchOnReconnect` is `true` by default; - queries that are fresh when the connection returns are left alone. ## Choosing the signal you feed it The docs example uses `isConnected`, which reports whether there is an active network. NetInfo also reports whether the internet is reachable, but that field is `null` while reachability is still unknown, for example before its first reachability check completes, and `!!null` is `false`: feeding it naively can mark the app offline at launch and pause every query until the check completes. Whatever you choose, pick one field deliberately and remember that `onlineManager` only gates *when* TanStack Query tries, it does not make a request succeed. ## Where the setup lives - Put it in a module imported by the app entry, beside the `focusManager` wiring, so it is in place before the first query mounts. - It is a singleton: one call covers every `QueryClient` in the app. - It does nothing about writes queued while offline beyond resuming paused mutations; how those are stored and replayed is a separate design.

  • A portfolio screen shows a spinner forever when opened in airplane mode. What is going on?
    With `onlineManager` wired to NetInfo, a query that mounts offline with no cached data is `status: 'pending'` and `fetchStatus: 'paused'`. A spinner keyed only to `isPending` never stops, because nothing is failing, it is waiting. Check `isPaused` (or `fetchStatus === 'paused'`) and render an offline state; the fetch starts by itself when NetInfo reports a connection.
  • On reconnect, what is the difference between a paused fetch continuing and a reconnect refetch?
    A paused fetch was already started and was waiting; on reconnect its retryer simply continues. A reconnect refetch is a new fetch started because an observed query is stale and `refetchOnReconnect` allows it. The continue happens regardless of `refetchOnReconnect`, which is why turning that option off does not stop a paused request from finishing.

saying these in an interview costs you the question

  • TanStack Query detects connectivity on iOS and Android with no extra setup
  • Offline queries go straight to error, so you must retry them by hand
  • Every useQuery call needs its own NetInfo subscription for reconnect to work
  • A reconnect refetches every cached query, fresh or stale, observed or not
  • isPending is enough to tell a user they are offline