Skip to content

Sentry.wrap breaks Fast Refresh (whole-tree remount on every save) — RN 0.85 / React 19.2 #6514

Description

@nriccar

Summary

Wrapping the app root with Sentry.wrap(App) breaks React Fast Refresh on the bare React Native CLI (New Architecture). With the wrap in place, every save — even editing an unrelated leaf component — remounts the entire component tree instead of preserving state. In a real app this means navigation resets to the initial route and any splash/entry animation replays on every edit, making the dev loop painful.

Fast Refresh itself is enabled and working — performReactRefresh runs. The problem is that Sentry.wrap swaps the AppRegistry root for an anonymous instrumented wrapper (TouchEventBoundary + ErrorBoundary + ReactNativeProfiler) that react-refresh cannot find in its component registry, so it falls back to remounting from the root.

Steps to reproduce

  1. Bare RN 0.85 app, New Arch. Root set up as:
    // App.tsx
    function App() { /* providers + navigator */ }
    export default Sentry.wrap(App)
    with Sentry.init(...) called at module load.
  2. Run the app in dev (Fast Refresh on).
  3. Navigate a couple of screens deep (or mount anything with local state / an entry animation).
  4. Edit any leaf component file (e.g. change a constant in a small presentational component that exports only that component) and save.

Expected: Fast Refresh updates the edited component in place and preserves state (you stay on the current screen).

Actual: The whole tree remounts — navigation returns to the initial route, top-level provider mount effects re-run, and any splash/entry animation replays.

Evidence

Controlled A/B against the running app (observed the JS console over CDP; a top-level provider logs a one-time banner from its mount effect, so its reappearance is a proxy for a full remount):

Root export Edit a leaf component Result
export default Sentry.wrap(App) provider mount effect re-fires whole tree remounts (state lost)
export default App only Metro's "rebuilding" log, no mount effect Fast Refresh preserves state ✅

The post-edit stack with Sentry.wrap shows react-refresh is the trigger and it is remounting, not preserving:

scheduleRefresh
performReactRefresh
performReactRefresh
…
commitHookPassiveMountEffects
commitHookEffectListMount   ← top-level provider's mount effect runs again

Bypassing only Sentry.wrap (everything else identical) fixes it, which isolates the cause to the wrap.

Workaround

Apply the wrap in release builds only; keep Sentry.init() in all environments so error capture is unaffected in dev:

export default __DEV__ ? App : Sentry.wrap(App)

This restores state-preserving Fast Refresh in dev. The tradeoff is that wrap-provided instrumentation (touch breadcrumbs, profiler, root error boundary) is inactive in dev only.

Environment

  • @sentry/react-native: 8.13.0
  • react-native: 0.85.3 (New Architecture)
  • react: 19.2.3
  • react-refresh: 0.14.2
  • metro / metro-runtime: 0.84.4
  • Platforms: iOS 18.5 simulator + Android emulator (API 34), same behavior on both.

Notes / questions

  • Is there a supported way for Sentry.wrap to register a stable/named component that react-refresh can track, so the wrap can stay active in dev without forcing a full remount?
  • Happy to provide a minimal repro if useful.

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status
    Waiting for: Product Owner

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions