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
- 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.
- Run the app in dev (Fast Refresh on).
- Navigate a couple of screens deep (or mount anything with local state / an entry animation).
- 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.
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 —
performReactRefreshruns. The problem is thatSentry.wrapswaps theAppRegistryroot 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
Sentry.init(...)called at module load.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):
export default Sentry.wrap(App)export default AppThe post-edit stack with
Sentry.wrapshows react-refresh is the trigger and it is remounting, not preserving: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: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.0react-native: 0.85.3 (New Architecture)react: 19.2.3react-refresh: 0.14.2metro/metro-runtime: 0.84.4Notes / questions
Sentry.wrapto register a stable/named component that react-refresh can track, so the wrap can stay active in dev without forcing a full remount?