How can a React Native app use NetInfo's type and details.isConnectionExpensive to postpone large photo uploads on metered networks?
answer
- not every connection costs the same
- details is null when disconnected
- Android: the OS metered flag
- iOS: cellular counts as expensive
- re-check when the state changes
basics
~20 sNetInfo's type says wifi, cellular or none, and details.isConnectionExpensive flags metered connections: on Android the OS's metered flag, on iOS true for cellular. Start bulk uploads only when reachable and not expensive, and re-check on each state change.
solid answer
~40 sA `NetInfoState` carries `type` (`'wifi'`, `'cellular'`, `'ethernet'`, `'none'`, `'unknown'` and others) and, when connected, a `details` object with `isConnectionExpensive`. The flag comes from the platform: on Android it is the OS's metered-network signal, so a Wi-Fi hotspot shared from a phone can be expensive too; on iOS the native module sets it for cellular connections. `details` is `null` when disconnected, so read it with optional chaining. In the field-inspection app, text reports sync immediately, but high-resolution photos wait until `isInternetReachable` is `true` and `isConnectionExpensive` is `false`, re-evaluated from `addEventListener` so the queue starts as soon as the inspector reaches Wi-Fi. I also give inspectors a "sync now anyway" override, because they know their data plan better than the flag does.
code
typescript · 17 linesimport NetInfo, { type NetInfoState } from '@react-native-community/netinfo';
export function canUploadPhotos(state: NetInfoState, allowCellular: boolean): boolean {
if (state.isInternetReachable !== true || state.type === 'none' || state.type === 'unknown') {
return false;
}
const expensive = state.details?.isConnectionExpensive ?? true;
return allowCellular || !expensive;
}
export function watchPhotoQueue(drain: () => void, allowCellular: () => boolean) {
return NetInfo.addEventListener((state) => {
if (canUploadPhotos(state, allowCellular())) {
drain();
}
});
}go deeper
Recall that NetInfo reports the connection type and a details.isConnectionExpensive flag, and that details is null when disconnected.
Explain where the flag comes from on each platform, why it beats checking type alone, and how to re-check it through addEventListener.
Design an upload policy: urgent data always, bulk data on cheap reachable networks, a user override and adaptive quality on slow generations.
Set the product's data-cost stance: defaults for metered users, what may never be deferred, and how network cost interacts with sync guarantees.
## What the state tells you about cost Every `NetInfoState` from `@react-native-community/netinfo` has: - **`type`** – one of `'unknown'`, `'none'`, `'cellular'`, `'wifi'`, `'bluetooth'`, `'ethernet'`, `'wimax'`, `'vpn'`, `'other'`. - **`details`** – `null` when the type is `'none'` or `'unknown'`; otherwise an object with **`isConnectionExpensive`** plus type-specific fields: for cellular, `cellularGeneration` (`'2g'` to `'5g'` or `null`) and `carrier`; for Wi-Fi, fields such as `ssid`, `strength` and `frequency`, several of which need extra permissions or configuration to be populated. ## Where isConnectionExpensive comes from The flag is not computed by NetInfo's JavaScript; it is what the native side reports: | Platform | Source of `isConnectionExpensive` | Consequence | |---|---|---| | Android | The OS's "active network is metered" signal | Metered Wi-Fi, such as a phone hotspot, is expensive too | | iOS | Set by the native module for cellular connections | Wi-Fi counts as not expensive | This is why checking `type === 'cellular'` alone is weaker than reading the flag: on Android, the user or the OS can mark a Wi-Fi network as metered, and the flag follows that. ## A policy for large uploads The field-inspection app produces two kinds of data: small JSON reports that must go out as soon as possible, and dozens of high-resolution photos per site. A sensible policy: 1. **Always send small, urgent payloads** whenever the internet is reachable. 2. **Defer bulk uploads** while `details?.isConnectionExpensive === true`. 3. **Re-evaluate on every change** by listening with `NetInfo.addEventListener`, so the photo queue starts the moment the inspector's phone joins office Wi-Fi. 4. **Give the user an override** ("Upload on mobile data"), stored as a setting, because an unlimited plan makes the flag irrelevant. 5. **Adapt quality** on slow cellular generations if needed, for example uploading a smaller rendition first when `cellularGeneration` is `'3g'` or `'2g'`. Persisting the queue and replaying it reliably is a separate offline-sync concern; the reachability layer only decides **when** the queue is allowed to drain. ## Combining cost with reachability The decision is really a small truth table over three inputs: | Reachable | Expensive | User allowed cellular | Bulk upload? | |---|---|---|---| | `false` | any | any | No | | `null` (unknown) | any | any | Wait for a real state | | `true` | `false` | any | Yes | | `true` | `true` | No | No, show "waiting for Wi-Fi" | | `true` | `true` | Yes | Yes | Writing it as one pure function, as in the example, makes the policy easy to unit test with hand-built `NetInfoState` objects, without touching a real network. ## Explaining the behaviour to users Deferring uploads silently confuses people: an inspector sees reports marked done but photos missing on the office dashboard. Make the policy visible: - Show a count of **waiting photos** and the reason, such as "Waiting for Wi-Fi". - Offer the **override** right there, not buried in settings. - When the queue starts draining on Wi-Fi, show progress so the inspector keeps the app open long enough. - If the device stays on expensive networks for days, remind the user rather than holding data indefinitely. ## Pitfalls - **Reading `details` when disconnected.** It is `null`, so `state.details.isConnectionExpensive` throws; use optional chaining. - **Treating `unknown` as cheap.** On launch the type can be `unknown`; wait for a real state before starting bulk work. - **Ignoring reachability.** An inexpensive Wi-Fi behind a captive portal is still not usable; combine the flag with `isInternetReachable === true` before starting bulk work. - **Assuming parity.** The two platforms compute the flag differently, so test the policy on both, including an Android phone on a metered hotspot. - **Expecting Wi-Fi names for free.** `ssid` needs location permission on Android, and on iOS the `shouldFetchWiFiSSID` configuration option plus Apple's own requirements; do not build logic on it without them. ## In an interview Explain what `type` and `isConnectionExpensive` are, where the flag comes from on each platform, and a concrete policy that defers bulk transfers, reacts to changes and lets the user override. That shows you design for users' data plans, not just for connectivity.
- An Android tester on a phone hotspot sees photos uploading as if on Wi-Fi in one build but deferred in another. What would you check?Whether the logic reads `type === 'wifi'` or `details.isConnectionExpensive`. A hotspot connection is Wi-Fi by type, but Android typically reports it as metered, so only the flag-based check defers the upload. The flag is the right signal for data cost.
- Why default a missing details object to expensive in the upload check?`details` is `null` when the state is disconnected or unknown, so the cost is unknown. Treating unknown as expensive errs on the side of not spending the user's data, and the listener re-runs the check as soon as a real state arrives.
saying these in an interview costs you the question
- type === 'wifi' guarantees the connection is free to use.
- details.isConnectionExpensive can be read safely when disconnected.
- isConnectionExpensive is computed the same way on iOS and Android.
- A cheap connection also means the internet is reachable.
- Wi-Fi SSID is always available in details without extra permissions.