Repository navigation
[iOS][Fabric] RCTSwitchComponentView onChange: dereferences _eventEmitter without a null check (EXC_BAD_ACCESS after view recycle) #59203
Description
Environment
- react-native 0.88.0 (first seen on 0.86.0; the file is byte-identical in both), New Architecture
- iOS 26.5.2 device (production crash), same code path on iOS 27
- Expo SDK 58 (not involved)
Description
-[RCTSwitchComponentView onChange:] (React/Fabric/Mounting/ComponentViews/Switch/RCTSwitchComponentView.mm:105-114) ends with
static_cast<const SwitchEventEmitter &>(*_eventEmitter) // line 113
.onChange(SwitchEventEmitter::OnChange{.value = static_cast<bool>(sender.on)});with no check that _eventEmitter is set. RCTViewComponentView -prepareForRecycle resets it (RCTViewComponentView.mm:779, _eventEmitter.reset();), while _props stays valid, so the earlier *_props dereference does not fail.
Production crash (0.86.0, line 114 of that version), iOS 26.5.2:
EXC_BAD_ACCESS (SIGSEGV) KERN_INVALID_ADDRESS at 0x0000000000000030
0 React std::weak_ptr<EventDispatcher const>::lock()
1 React EventEmitter::dispatchEvent(...)
4 React SwitchEventEmitter::onChange(...)
5 React -[RCTSwitchComponentView onChange:]
6 UIKitCore -[UIApplication sendAction:to:from:forEvent:]
9 UIKitCore -[UISwitchModernVisualElement sendStateChangeActions]
11 UIKitCore -[UISwitchModernVisualElement _handleLongPressWithGestureLocationInBounds:...]
12 UIKitCore -[_UIAfterCACommitBlock run]
0x30 is a null this plus the eventDispatcher_ offset in EventEmitter.
Reproduction
Intermittent, not reproduced on demand. Conditions: iOS 26+, where the redesigned UISwitch (UISwitchModernVisualElement) delivers the state-change action from a deferred _UIAfterCACommitBlock instead of synchronously on touch-up (seen when the switch is dragged or long-pressed rather than tapped). If React re-renders and recycles the Switch view between the gesture ending and that block running, prepareForRecycle has already cleared _eventEmitter and onChange: runs on the recycled view.
Root cause
Missing null guard. Sibling components in the same directory tree already have it: RCTScrollViewComponentView, RCTImageComponentView, RCTModalHostViewComponentView, RCTVirtualViewComponentView (if (!_eventEmitter) { return; }), and RCTTextInputComponentView tests _eventEmitter before use at several sites. Switch is the outlier.
Proposed fix
--- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Switch/RCTSwitchComponentView.mm
+++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Switch/RCTSwitchComponentView.mm
@@ -110,6 +110,9 @@ - (void)onChange:(UISwitch *)sender
return;
}
+ if (!_eventEmitter) {
+ return;
+ }
static_cast<const SwitchEventEmitter &>(*_eventEmitter)
.onChange(SwitchEventEmitter::OnChange{.value = static_cast<bool>(sender.on)});
}We carry this as a local patch. Dropping the event is the right behaviour: the view has been recycled and no longer represents that React node.
Disclosure: drafted with AI assistance (Claude) from a patch we carry locally; code references were checked against the published package sources. Happy to clarify or open a PR.
Activity
react-native-bot commentedon Oct 19, 2026
Warning
Missing reproducer: We could not detect a reproducible example in your issue report. Reproducers are mandatory and we can accept only one of those as a valid reproducer:
You can read more about about it on our website: How to report a bug.
Fix Switch crash when onChange fires after view recycle (react#59216)