skip to content

In React Native, how do you read the network state once and subscribe to its changes with NetInfo's fetch, addEventListener and useNetInfo?

level: juniorimportance: must knowfreq 45%

answer

  1. one-off promise vs subscription vs hook
  2. fetch may return the cached state
  3. refresh forces a new native query
  4. addEventListener returns a function
  5. hook starts as unknown with nulls

basics

~20 s

NetInfo.fetch() resolves to the current NetInfoState (the cached latest one if available; refresh() forces a new query). NetInfo.addEventListener(cb) calls back now and on every change and returns an unsubscribe function. useNetInfo() wraps that subscription in a hook that starts as unknown.

solid answer

~40 s

`NetInfo.fetch()` returns a promise of a `NetInfoState`; if the library already holds a latest state it resolves to that, and `NetInfo.refresh()` forces a fresh native query. For ongoing updates, `NetInfo.addEventListener(listener)` calls the listener soon after subscribing with the latest state and then on each change, and returns an **unsubscribe function** that you call on cleanup. In components, `useNetInfo()` does that subscription for you and returns the state; its first render is `type: 'unknown'` with `isConnected` and `isInternetReachable` set to `null`, so the UI must handle unknown. All three share one global singleton. I use `fetch` for a one-off decision, such as before starting a large sync, `addEventListener` in non-React code like a sync service, and `useNetInfo` for UI like the field-inspection banner.

code

typescript · 19 lines
typescript
import NetInfo from '@react-native-community/netinfo';

export function startSyncWatcher(onBackOnline: () => void) {
  let wasReachable: boolean | null = null;
  const unsubscribe = NetInfo.addEventListener((state) => {
    if (wasReachable === false && state.isInternetReachable === true) {
      onBackOnline();
    }
    if (state.isInternetReachable !== null) {
      wasReachable = state.isInternetReachable;
    }
  });
  return unsubscribe; // a plain function, call it to stop listening
}

export async function canStartBulkSync(): Promise<boolean> {
  const state = await NetInfo.fetch();
  return state.isInternetReachable !== false;
}

go deeper

for a junior

Recall the three entry points: fetch for a one-off check, addEventListener for updates with its unsubscribe function, and useNetInfo in components.

for a middle

Explain the singleton, that fetch may resolve cached state while refresh forces a query, and that useNetInfo starts as unknown with nulls.

for a senior

Keep configuration in one startup call, avoid the useNetInfo(configuration) teardown trap, and use useNetInfoInstance when a screen needs isolated settings.

for a principal

Decide where network state lives in the app: one service feeding stores and sync, rather than scattered hooks each reacting differently to the same transitions.

## The library and its singleton Network status in React Native comes from the community library `@react-native-community/netinfo` (core React Native has no NetInfo module). It keeps **one global state manager** that listens to the native module and, where needed, runs internet-reachability checks. Its main entry points all read from that singleton: | API | Returns | Use it for | |---|---|---| | `NetInfo.fetch()` | `Promise<NetInfoState>` | A one-off check at a decision point | | `NetInfo.refresh()` | `Promise<NetInfoState>` | Forcing a fresh native query | | `NetInfo.addEventListener(fn)` | an unsubscribe **function** | Services and non-React code | | `useNetInfo()` | `NetInfoState` | Components that render network status | ## fetch and refresh `NetInfo.fetch()` resolves with the **latest state the library already knows** when one exists; otherwise it queries native code. That is fast, but it can be slightly behind reality right after a change. `NetInfo.refresh()` always asks native code again, and concurrent calls share one in-flight request. `fetch` also accepts an optional interface name to query a specific interface such as Wi-Fi. In the field-inspection app, a reasonable use is checking the state once before starting a bulk photo sync: 1. `const state = await NetInfo.fetch();` 2. If `state.isInternetReachable === false`, skip the sync and keep the queue. 3. Otherwise start, and let request failures handle the rest. ## addEventListener `NetInfo.addEventListener(listener)` registers a callback that receives a `NetInfoState`: - It is called **soon after subscribing** with the latest state, so you do not need a separate `fetch` to initialise. - It is then called **whenever the state changes**; the library's docs warn not to assume identical timing across devices or platforms. - It returns a **function**, not an object with `remove()`. Calling that function unsubscribes. Forgetting to call the unsubscribe function leaks the listener and keeps a closure (and whatever it references) alive for the app's lifetime. ## useNetInfo `useNetInfo()` subscribes in an effect, unsubscribes on unmount and returns the current state. Its **initial state** is: - `type: 'unknown'` - `isConnected: null` - `isInternetReachable: null` - `details: null` So the first render always looks unknown, and components must not interpret that as offline. Two refinements: - **Configuration.** `useNetInfo(configuration)` calls `NetInfo.configure` on every render, and `configure` tears down the singleton and stops previously added listeners. Configure once at app start instead. - **Isolated instances.** `useNetInfoInstance(isPaused, configuration)` runs a separate state manager for one component, with its own configuration and a `refresh` function, without touching the global singleton. ## Testing code that uses NetInfo NetInfo is a native module, so Jest tests need its mock. The library ships one at `@react-native-community/netinfo/jest/netinfo-mock.js`, which you register from your Jest setup file. With the mock in place, tests can drive the listener or the hook with hand-built states, for example a transition from `isInternetReachable: false` to `true`, and assert that the sync service reacts exactly once. ## Where each API belongs in an app A common structure for the field-inspection app: 1. **A connectivity service**, started once at launch, subscribes with `NetInfo.addEventListener`, keeps the latest state and notifies the sync queue on transitions from unreachable to reachable. 2. **UI components** such as the offline banner call `useNetInfo()` and only render. 3. **Decision points**, such as "start bulk sync now?" or a Retry button, call `NetInfo.fetch()`, or `NetInfo.refresh()` right after a user action where freshness matters. This keeps side effects out of components and gives one place to log transitions for debugging. It also means only one listener drives syncing; if several components each started uploads from their own hook, a single reconnect could trigger duplicate work. On cleanup, services call the function returned by `addEventListener`, and hooks clean up on their own when the component unmounts. ## Reading the state object Beyond the two booleans, a state has `type` (`'wifi'`, `'cellular'`, `'none'`, `'unknown'` and others) and `details`, which is `null` when disconnected and otherwise holds type-specific fields such as `isConnectionExpensive` and, for cellular, `cellularGeneration`. ## Interview checklist Name the three APIs and when each fits, mention that `fetch` may return cached data while `refresh` forces a query, show the **unsubscribe function** from `addEventListener`, and point out that `useNetInfo` starts unknown.

  • A teammate writes const sub = NetInfo.addEventListener(cb); and later calls sub.remove(). What happens?
    It throws, because NetInfo's `addEventListener` returns a plain unsubscribe function, not a subscription object. Call `sub()` instead. This differs from React Native core APIs such as `AppState.addEventListener`, which return an object with `remove()`.
  • When would you call NetInfo.refresh() instead of fetch()?
    When a decision depends on the state right now, for example immediately after the user taps Retry, because `fetch` can resolve with the cached latest state. `refresh` forces a new native query, and parallel calls share one request.

saying these in an interview costs you the question

  • NetInfo is imported from 'react-native' in current versions.
  • NetInfo.fetch() always performs a fresh native query.
  • addEventListener returns a subscription object with a remove() method.
  • useNetInfo's first render already contains the real network state.
  • Passing a configuration to useNetInfo on every render is harmless.