Skip to content

Network requests not captured on Expo SDK 56 #1612

@ShanavasPS

Description

@ShanavasPS

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 appsexpo is loaded at startup (e.g. via expo-router/entry), so the fetch swap 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 expo package at runtime. Importing an Expo package triggers expo/src/Expo.fx, which loads the Winter runtime and replaces global.fetch. For example, calling useCameraPermissions() from expo-camera (which imports from expo) 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 like useRef<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 against master, 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 — patches XMLHttpRequest.prototype.open / send / setRequestHeader
  • lib/reactotron-react-native/src/plugins/networking.ts — relies solely on XHRInterceptor.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

  1. 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/fetch is the globalfetch).
  2. Configure Reactotron with .useReactNative() (networking enabled) and .connect().
  3. Make any fetch(...) request (in a bare app, ensure the expo package has been imported — e.g. useCameraPermissions() from expo-camera).
  4. 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:

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions