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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions