Repository navigation
Network requests not captured on Expo SDK 56 #1612
Description
Describe the bug
Summary
On Expo SDK 56, Reactotron's networking timeline shows no network requests. SDK 56 installs expo/fetch as the global fetch by default, and expo/fetch is a native implementation that does not go through XMLHttpRequest. Reactotron's network instrumentation only patches XMLHttpRequest, so it never observes any traffic. Console log/display events are unaffected — only the network timeline is empty.
This affects both project types:
- Managed Expo apps —
expois loaded at startup (e.g. viaexpo-router/entry), so thefetchswap happens from app launch and the network timeline is empty from the start. - Bare React Native apps that use Expo packages — the swap runs the first time anything imports the
expopackage at runtime. Importing an Expo package triggersexpo/src/Expo.fx, which loads the Winter runtime and replacesglobal.fetch. For example, callinguseCameraPermissions()fromexpo-camera(which imports fromexpo) is enough. So in a bare app the network timeline can appear to work and then go empty once such a code path is loaded. (A type-only reference likeuseRef<CameraView>(null)is elided by the Babel pipeline and does NOT trigger it — only a runtime import does.)
Environment
reactotron-react-native: 5.2.0 (also confirmed againstmaster, last pushed 2026-05-28)- Reactotron desktop: 3.11.0
- Expo SDK: 56
- React Native: 0.85.3 (New Architecture)
- Platform: iOS & Android
- Reproduced on both a managed Expo app and a bare RN app that uses Expo
packages (hybrid bare workflow)
Root cause
Expo SDK 56 makes expo/fetch the default globalThis.fetch (Expo docs, SDK 56 changelog). It's a fully native fetch that bypasses XMLHttpRequest.
Reactotron's interceptor patches only XHR:
lib/reactotron-react-native/src/xhr-interceptor.ts— patchesXMLHttpRequest.prototype.open/send/setRequestHeaderlib/reactotron-react-native/src/plugins/networking.ts— relies solely onXHRInterceptor.enableInterception()
The assumption is even documented in xhr-interceptor.ts:
"This supports interception with XMLHttpRequest API, including Fetch API and any other third party libraries that depend on XMLHttpRequest."
That assumption — that fetch is built on XHR — no longer holds under SDK 56's expo/fetch.
Steps to reproduce
- An SDK 56 app — either a managed Expo app, or a bare RN app that imports an Expo package at runtime (default config, so
expo/fetchis the globalfetch). - Configure Reactotron with
.useReactNative()(networking enabled) and.connect(). - Make any
fetch(...)request (in a bare app, ensure theexpopackage has been imported — e.g.useCameraPermissions()fromexpo-camera). - Observe the Reactotron timeline.
Expected
The request appears in the networking timeline.
Actual
Network logs are missing in Timeline. App stays connected.
Workaround
Until the fix lands, add this line to your .env file:
EXPO_PUBLIC_USE_RN_FETCH=1
This is Expo's own opt-out — it keeps React Native's XHR-based fetch as the global, which Reactotron's existing networking plugin already instruments, so requests show up in the timeline again.
Trade-off: the app forgoes expo/fetch's improvements (brotli/gzip/zstd decompression, AbortSignal.timeout). Because EXPO_PUBLIC_* vars are inlined into the JS bundle at build time, prefer a dev-only env file (e.g..env.development) so the opt-out doesn't ship to production.
Related / prior art
Sentry hit and fixed this exact problem:
- Issue: fix(tracing): Enable fetch instrumentation when Expo SDK 56 expo/fetch is active getsentry/sentry-react-native#6225
- Fix PR: fix(tracing): Enable fetch instrumentation when expo/fetch is active getsentry/sentry-react-native#6226
Their approach: detect that the global fetch is the native Expo implementation via the marker globalThis.fetch[Symbol.for('expo.builtin')] === true (a new isExpoFetchEnabled() helper), then enable fetch-layer instrumentation when detected (they flipped traceFetch and fetch-breadcrumb defaults from false to isExpoFetchEnabled()).
Possible fix
The catch for Reactotron: Sentry already had a fetch-instrumentation layer and only needed to change a default. Reactotron is XHR-only (xhr-interceptor.ts / networking.ts patch XMLHttpRequest exclusively), so there's no existing fetch layer to enable — a fetch interceptor would need to be added.
A minimal version: wrap globalThis.fetch when the expo.builtin marker is present and emit reactotron.apiResponse(request, response, duration) — the same event the XHR path produces. Gating on the marker avoids double-counting when EXPO_PUBLIC_USE_RN_FETCH=1 reverts the global to RN's XHR-based fetch.
Reactotron version
5.2.0
Activity
ShanavasPS commentedon Jun 9, 2026
A PR for this is up: #1613 — adds a FetchInterceptor (mirroring the existing xhr-interceptor.ts) that wraps the global fetch only when it's the expo.builtin implementation, wired into the networking plugin behind a new ignoreExpoFetch option.
joshuayoes commentedon Aug 13, 2026
Status update for anyone landing here:
The base fix is in feat(reactotron-react-native): track Expo expo/fetch in the networking plugin #1613 (approved, merging soon): the networking plugin now detects Expo SDK 56+'s
expo/fetchglobal and tracks it, with no double-reporting alongside XHR.If you use expo-router (the default
create-expo-apptemplate): the router's startup polyfill re-wrapsglobalThis.fetchin a way that hides it from the automatic detection, so fetch tracking silently won't kick in. Workaround — pass fetch explicitly:Caveats: capture
globalThis.fetchin your app code (which runs afterexpo-router/entry), and don't use this together withEXPO_PUBLIC_USE_RN_FETCH=1(that fetch is XHR-backed and already tracked — you'd get duplicates). Full docs land with fix(reactotron-react-native): harden fetch interceptor error isolation and restore #1615.Automatic detection for expo-router apps is in the works — the router wrapper carries its own marker symbol we can recognize; it needs a guard against double-reporting first, so it's the next iteration after feat(reactotron-react-native): track Expo expo/fetch in the networking plugin #1613/fix(reactotron-react-native): harden fetch interceptor error isolation and restore #1615 ship.
Also worth knowing: Expo SDK 56 before 56.0.19 has a
Response.clone()bug ([expo] Fix bodyUsed leaking across siblings when fetch Response is cloned twice (#46366) expo/expo#46397) that can throw a spurious "body already used" while any network inspector reads bodies off a clone — upgrade to 56.0.19+ or SDK 57 if you hit that.feat(reactotron-react-native): track Expo expo/fetch in the networkin…
joshuayoes commentedon Aug 13, 2026
📦 Released: reactotron-react-native@5.3.0 adds Expo
expo/fetchtracking to the networking plugin (#1613), and 5.3.1 hardens the fetch interceptor's error isolation and restore behavior (#1615). Both are on npm now.Note for expo-router apps: the router setup drops the
expo.builtinsymbol fromglobalThis.fetch, so automatic detection doesn't kick in yet. Until auto-detection lands, pass fetch explicitly:Verified end-to-end against the published npm artifact on an Expo SDK 57 expo-router app (iOS simulator): fetch requests appear in the timeline with correct method/status/body, XHR is not double-reported, malformed percent-encoded URLs no longer throw, and the app runs fine with Reactotron disconnected.