skip to content

Networking & Connectivity

How a React Native app talks to servers: fetch and XMLHttpRequest over the native stack, WebSocket clients, NetInfo reachability and a server-state cache. Mobile networks drop and change constantly.

part ofReact Nativeoverview, primer and where to startread it →
on this pageshow

explore

questions

19

In a React Native app on the Android emulator, why does fetch('http://localhost:3000/api') fail, and which address reaches your development machine?

level: juniorimportance: must knowfreq 62%

answer

  1. whose loopback is localhost?
  2. the emulator is its own device
  3. a fixed 10.0.x.x alias for the host
  4. iOS Simulator shares the Mac's network
  5. real phone: LAN IP, server on 0.0.0.0

basics

~20 s

On the Android emulator, localhost is the emulator itself, not your computer. The stock emulator reaches the host's loopback through 10.0.2.2; the iOS Simulator shares the Mac's network, so localhost works there, and a physical phone needs the computer's LAN IP.

solid answer

~40 s

The Android emulator is a separate virtual device with its own network stack, so `localhost` and `127.0.0.1` inside it point at the emulator, where nothing listens on port 3000. The stock emulator exposes the host's loopback at the fixed alias `10.0.2.2` (Genymotion uses `10.0.3.2`). The iOS Simulator runs as a process on the Mac and shares its network, so `localhost` works there. A physical phone needs the computer's LAN IP, and the dev server must listen on `0.0.0.0` rather than only `127.0.0.1`. The confusing part is that the Metro bundle loads fine: React Native's Android dev-support code already swaps in `10.0.2.2` for the Metro host, but it never rewrites your own `fetch` URLs. I pick the base URL per platform with `Platform.select` behind an environment switch.

code

typescript · 11 lines
typescript
import { Platform } from 'react-native';

const DEV_HOST =
  process.env.EXPO_PUBLIC_DEV_API_HOST ??
  Platform.select({
    android: 'http://10.0.2.2:3000',
    ios: 'http://localhost:3000',
    default: 'http://localhost:3000',
  });

export const API_URL = __DEV__ ? DEV_HOST : 'https://api.example.com';

go deeper

for a junior

Recall that localhost on the Android emulator is the emulator itself, that 10.0.2.2 reaches the host, and that the iOS Simulator can use localhost directly.

for a middle

Explain why Metro connects but your fetch fails: dev-support code picks the emulator alias for the dev server only. Cover physical devices, LAN IPs and servers bound to 0.0.0.0.

for a senior

Show a base-URL module driven by configuration with per-platform fallbacks, and walk through diagnosing a device that cannot reach a laptop: bind address, firewall, VPN, client isolation.

for a principal

Frame local API access as developer-experience infrastructure: shared dev hosts or tunnels versus per-laptop servers, and keeping one configuration path so no hard-coded host ever ships.

## What localhost means inside a device `localhost` always means **the machine the code is running on**. In a React Native app, your JavaScript runs on the device (or the emulator), and `fetch` hands the URL to the device's native HTTP stack. So the question is never "where is my API?" but "which network stack resolves this hostname?" - **Android emulator** – a full virtual device with its own virtual network. Its `localhost` is the emulator's own loopback interface. Your API server runs on the host computer, not in the emulator, so a request to `http://localhost:3000` finds nothing listening and fails with a network error. - **iOS Simulator** – not a virtual machine. It runs as a macOS process and shares the Mac's network interfaces, so `localhost` in the simulator *is* the Mac, and the same URL works. - **Physical device** – a separate computer on your Wi-Fi. Neither `localhost` nor the emulator alias exists for it; it needs your computer's address on the local network. ## The host aliases The Android emulator routes one special address to the host's loopback. React Native's own Android sources name these constants (`EMULATOR_LOCALHOST`, `GENYMOTION_LOCALHOST`): | Where the app runs | Address that reaches your computer | Notes | |---|---|---| | Stock Android emulator | `10.0.2.2` | A fixed alias for the host's `127.0.0.1` | | Genymotion emulator | `10.0.3.2` | Same idea, different virtual network | | iOS Simulator | `localhost` / `127.0.0.1` | Shares the Mac's network | | Physical phone on Wi-Fi | The computer's LAN IP, e.g. `192.168.1.20` | Server must listen on all interfaces | Because `10.0.2.2` maps onto the host's loopback, a server bound only to `127.0.0.1` is still reachable from the emulator. A physical phone, however, arrives on the LAN interface, so the server must bind to `0.0.0.0` (all interfaces), the firewall must allow the port, and phone and computer must share a network. ## Why the bundle loads but your API call does not This is the part that confuses people in the interview and in real life. In a debug build, the app downloads its JavaScript from **Metro** on your computer, and that works on the emulator without any configuration. The reason is that React Native's Android dev-support code detects a stock emulator and uses `10.0.2.2` for the Metro host automatically (and `10.0.3.2` on Genymotion). That rewrite applies **only to the dev server connection**. Your `fetch('http://localhost:3000/api')` is passed to the native networking layer exactly as written. So "Metro works, my API doesn't" is the expected symptom, not a sign that networking is broken. ## Picking the base URL Keep the host decision in one module instead of scattering URLs through screens: 1. Read the development host from configuration (an environment variable or app config), so a teammate on a physical phone can point it at their own LAN IP. 2. Fall back per platform with `Platform.select`, which accepts `android`, `ios`, `native` and `default` keys. 3. Use the production HTTPS URL outside `__DEV__`. A few more things that commonly get in the way: - A plain `http://` URL also has to satisfy each platform's cleartext-traffic rules, which is a separate transport-security topic. - Port forwarding over USB is another way to reach the host from an Android device; it belongs with device setup rather than with `fetch`. - A VPN on the computer or a guest Wi-Fi with client isolation can block phone-to-laptop traffic even when the IP is right. ## Debugging it step by step When a request from the app fails and you suspect the host, narrow it down in order: 1. **Open the URL from the device's own browser.** If Chrome inside the emulator cannot load `http://10.0.2.2:3000/health`, the problem is the network path, not your JavaScript. 2. **Log the resolved base URL at startup** in development, so a stale environment value or a wrong `Platform.select` branch is visible immediately. 3. **Check the server's bind address** with the server's own startup log: `127.0.0.1` is fine for emulators, but a physical phone needs `0.0.0.0`. 4. **Read the error, not just the spinner.** A connection refused arrives quickly; a request that hangs points at a firewall or an unreachable address, which is also why every request should have a timeout. ## How to talk about it in an interview A strong answer states the mechanism in one line (**the emulator has its own loopback**), gives the alias (**`10.0.2.2`**), contrasts the iOS Simulator (**shared network**) and the physical device (**LAN IP plus a server listening on all interfaces**), and then shows that you centralise the base URL. Mentioning why Metro still works shows you understand that React Native's tooling and your application code resolve hosts separately.

  • The same URL with 10.0.2.2 works on the emulator but times out on a physical Android phone. What do you check?
    `10.0.2.2` exists only inside the emulator's virtual network, so the phone needs the computer's LAN IP. Then check that the server listens on `0.0.0.0`, not just `127.0.0.1`, that the host firewall allows the port, and that phone and computer share a network without client isolation or a VPN in between.
  • Why doesn't the iOS Simulator need an alias at all?
    The iOS Simulator is not a virtual machine with its own network; it runs as a macOS process and uses the Mac's network interfaces. `localhost` inside it is therefore the Mac itself, so a server on the Mac's port 3000 answers directly.
  • Where should the development host live so the team does not edit code to switch devices?
    In configuration: an environment variable or app config value read by one API module, with `Platform.select` defaults as a fallback. Each developer overrides it locally, for example with their LAN IP, and release builds always use the production HTTPS URL.

The emulator is a flat inside your house with its own front door: shouting 'hello, this flat' reaches only the flat. 10.0.2.2 is the intercom button labelled 'main house', the one fixed way to ring the building you are standing in.

saying these in an interview costs you the question

  • The emulator shares the computer's network, so localhost reaches the host.
  • React Native rewrites localhost in fetch URLs automatically in debug builds.
  • 10.0.2.2 also works from a physical Android phone on the same Wi-Fi.
  • If Metro loads the bundle, the API must be reachable at localhost too.
  • The iOS Simulator needs 10.0.2.2 just like the Android emulator.
open as a page

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%

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.

open as a page

In a React Native screen, how do you open a WebSocket, handle its onopen, onmessage and onclose events, and close it cleanly?

level: juniorimportance: must knowfreq 48%

basics

~20 s

Create the global WebSocket in a useEffect, send only after onopen, parse event.data in onmessage and call ws.close() in the cleanup. React Native's send() throws while still connecting, and a failed connection fires error, then close with code 1006.

open as a page

Why does a React Native app's fetch call never fail with a CORS error, when the same request from a web page does?

level: middleimportance: must knowfreq 52%

basics

~20 s

CORS is a rule browsers enforce on pages from another origin. A React Native app's fetch uses the native HTTP stack, which has no page origin and no CORS check, so CORS headers neither block nor protect anything; the API must authenticate callers.

open as a page

With React Native's NetInfo, what is the difference between isConnected and isInternetReachable, and which should drive an offline banner?

level: middleimportance: must knowfreq 55%

basics

~20 s

isConnected says the device has an active network link; isInternetReachable says that link actually reaches the internet, and is null while unknown. Drive an offline banner from isInternetReachable === false, never from null, and still treat failed API calls as the final word.

open as a page

With TanStack Query v5 in React Native, why does a stale portfolio query never refetch when the app returns to the foreground?

level: middleimportance: must knowfreq 55%

basics

~20 s

TanStack Query's default focus signal is the browser's visibilitychange event, which React Native never fires, so the app always looks focused and no focus refetch happens. Wire focusManager to AppState, reporting focused only when the status is 'active'.

open as a page

A React Native live-auction app stops receiving bids after the user returns from the background; why, and how should the WebSocket reconnect?

level: seniorimportance: must knowfreq 45%

basics

~20 s

A suspended app's socket stops being serviced and is often dropped without a close JavaScript ever sees. On returning to active, replace a suspect socket, reconnect with capped exponential backoff plus jitter, resubscribe, and fetch the bids missed meanwhile.

open as a page

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%

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.

open as a page

In a React Native freelancer-marketplace app, how do you upload a profile photo from a local file URI and show upload progress?

level: middleimportance: should knowfreq 46%

basics

~20 s

Append { uri, name, type } to a FormData so native code streams the file from disk, and send it with XMLHttpRequest or axios, since fetch has no upload progress event. In Expo SDK 56+, the global expo/fetch rejects uri parts.

open as a page

In React Native, fetch() has no timeout option; how do you stop a request hanging on a bad mobile network, and what does aborting cancel?

level: middleimportance: should knowfreq 48%

basics

~20 s

Pass an AbortController's signal to fetch and call abort() from a setTimeout, clearing the timer in finally; React Native 0.87 also has AbortSignal.timeout(ms). Aborting rejects the promise with an abort error and cancels the underlying native HTTP request.

open as a page

In an Expo app, how does expo-network's useNetworkState differ from NetInfo's useNetInfo, and when is it not enough?

level: middleimportance: should knowfreq 24%

basics

~20 s

expo-network's useNetworkState returns { type, isConnected, isInternetReachable }, starting as an empty object. On Android reachability comes from the OS's validated-network check, but on iOS isInternetReachable always equals isConnected, and there is no probe to configure; NetInfo covers those gaps.

open as a page

With TanStack Query v5 wired to AppState and NetInfo in React Native, how many portfolio requests fire when focus and reconnect events arrive together?

level: middleimportance: should knowfreq 30%

basics

~20 s

One per query key. The focus and reconnect triggers both refetch with cancelRefetch set to false, so the second trigger finds a fetch already in flight and joins its promise instead of sending another request.

open as a page

What does React Native's WebSocket constructor accept that the browser's does not, and when would you use its third argument?

level: middleimportance: should knowfreq 36%

basics

~20 s

React Native's WebSocket takes a third argument, { headers }, sent on the upgrade request, so an app can set Authorization or custom headers a browser WebSocket cannot. Native cookies are attached too, and a non-standard ping() method exists.

open as a page

In React Native NetInfo, what do reachabilityUrl and the other reachability options in NetInfo.configure control, and why point them at your own API?

level: seniorimportance: should knowfreq 22%

basics

~20 s

They configure NetInfo's own HTTP probe: reachabilityUrl, method and headers, a reachabilityTest on the response, polling intervals and a request timeout. Pointing it at your API's health endpoint makes 'reachable' mean 'our backend answers'. Call NetInfo.configure once at startup.

open as a page

A React Native investing app persists its TanStack Query v5 cache to AsyncStorage, yet the portfolio is empty after a cold start; what are the usual causes?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Usually one of four causes: gcTime shorter than maxAge dropped entries before the save, the store was discarded for age or a buster mismatch, the last refetch errored so the query was not saved, or queries ran before the async restore.

open as a page

Why does a React Native app need an application-level heartbeat on its WebSocket, even though React Native's WebSocket has a ping() method?

level: seniorimportance: should knowfreq 26%

basics

~20 s

React Native's ping() sends a protocol ping, but no pong reaches JavaScript, and Android's client has no read timeout, so a dead socket can look OPEN. An app-level heartbeat the server answers within a deadline lets your code detect it and reconnect.

open as a page

How can a React Native app use NetInfo's type and details.isConnectionExpensive to postpone large photo uploads on metered networks?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

NetInfo'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.

open as a page

In a React Native app using TanStack Query v5, when would you set networkMode to 'offlineFirst' or 'always' instead of the default 'online'?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

Keep 'online' for network queries. Use 'offlineFirst' when the queryFn might answer offline: it runs once and only pauses retries. Use 'always' when the queryFn never touches the network, such as an on-device storage read.

open as a page