You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When testing with the fs experiment demo (as of 5.0) on Android, I was seeing touch event seemingly firing twice. I was able to reproduce the issue after tweaking the basic demo slightly, see video below
video-demo-28.mp4
Analysis
This seems to be related to how fabric on android resolves view tags. Notably, to reproduce the demo, I added a bunch of empty padding views such that Host Button has tag 28 and the 3rd button in the sandbox also has tag 28.
Adding some AI-assisted analysis:
The host and sandbox Fabric surfaces share the same global React view tag namespace on Android. In Fabric, React tags are used as Android view IDs (view.setId(reactTag)). Both surfaces allocate tags sequentially starting from low numbers. When a sandbox view and a host view are assigned the same tag (e.g., both get tag 20), touch events targeting that tag in the sandbox are also resolved by the host's Fabric renderer to its own view at the same tag.
Why it happens
The Android event dispatch flow for a touch inside the sandbox:
Activity.dispatchTouchEvent() delivers the MotionEvent to the window
The event propagates down to the host's ReactSurfaceView.dispatchTouchEvent()
The host's ReactSurfaceView feeds the event into Fabric's C++ touch handler, which does hit-testing using the shadow tree and React tags — this is where the bleed occurs
ReactSurfaceView then calls super.dispatchTouchEvent(), propagating down the Android view tree
The event reaches SandboxReactNativeView, which forwards to the sandbox's ReactSurfaceView
The sandbox's Fabric processes the event correctly for its own surface
The host processes the event at step 3 before the sandbox sees it at step 5. Any fix applied at step 5 or later cannot prevent the bleed.
Why it's layout-dependent
The bug manifests only when a host touchable component happens to have the same React tag as a sandbox internal view. Adding or removing views in the host shifts tag assignments, which can trigger or hide the collision. This makes the bug appear intermittent and layout-dependent.
Why iOS is not affected
On iOS, Fabric uses UIView-based hit testing (hitTest:withEvent:) which walks the view hierarchy and returns the deepest view containing the touch point. The sandbox's RCTSurfaceHostingView naturally clips hit testing to its bounds. iOS's touch dispatch doesn't resolve tags globally across surfaces the way Android's ReactSurfaceView does.
Reproduction
Was able to reproduce following changes to the basic demo:
App.tsx
import SandboxReactNativeView from '@callstack/react-native-sandbox'
import React from 'react'
import {SafeAreaView, ScrollView, StyleSheet, Text, View} from 'react-native'
import Toast from 'react-native-toast-message'
import TouchBleedRepro from './TouchBleedRepro'
const DemoApp: React.FC = () => {
return <TouchBleedRepro />
}
const styles = StyleSheet.create({})
export default DemoApp
/**
* Minimal reproduction of touch bleed between host and sandbox surfaces.
*
* The bug: When the host's React view tags happen to be numerically adjacent
* to the sandbox's ReactSurfaceView ID, touch events from the sandbox
* "bleed" into the host — causing host buttons to show press highlights
* (and sometimes fire handlers) when sandbox buttons are pressed.
*
* The padding Views below push the host button's React tag into the
* collision zone with the sandbox's ReactSurfaceView ID (~31).
* Remove the padding Views and the bleed disappears.
*/
import SandboxReactNativeView from '@callstack/react-native-sandbox'
import React, {useState} from 'react'
import {
SafeAreaView,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native'
export default function TouchBleedRepro() {
const [pressCount, setPressCount] = useState(0)
return (
<SafeAreaView style={styles.container}>
<ScrollView>
{/* Host section */}
<View style={styles.section}>
<Text style={styles.title}>Touch Bleed Repro</Text>
<Text style={styles.subtitle}>
Press the sandbox button below. Watch if the host button highlights.
</Text>
{/*
* Adjust padding view count to push host button tag
* near a sandbox internal button tag.
* Sandbox buttons are at tags: 12, 20, 28, 36, 46
*/}
{/* Padding to push host button tag to id 20 (matching 2nd button in sandbox) */}
<View style={styles.pad} />
<View style={styles.pad} />
<View style={styles.pad} />
{/* Padding to push host button tag to id 28 (matching 3rd button in sandbox) */}
{/*
<View style={styles.pad} />
<View style={styles.pad} />
<View style={styles.pad} />
<View style={styles.pad} />
<View style={styles.pad} />
<View style={styles.pad} />
<View style={styles.pad} />
*/}
<TouchableOpacity
style={styles.hostButton}
onLayout={(e) => {
console.log(`[REPRO] Host button nativeTag=${(e as any).nativeEvent?.target}`)
}}
onPress={() => setPressCount(c => c + 1)}>
<Text style={styles.buttonText}>Host Button</Text>
</TouchableOpacity>
<Text style={styles.counter}>Host press count: {pressCount}</Text>
</View>
{/* Sandbox section */}
<View style={styles.sandboxSection}>
<Text style={styles.title}>Sandbox</Text>
<SandboxReactNativeView
style={styles.sandbox}
componentName={'SandboxedDemo'}
jsBundleSource={'sandbox.android.bundle'}
onError={error => {
console.warn('Sandbox error:', error)
}}
/>
</View>
</ScrollView>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
section: {
padding: 16,
},
title: {
fontSize: 20,
fontWeight: '700',
marginBottom: 8,
},
subtitle: {
fontSize: 14,
color: '#666',
marginBottom: 12,
},
pad: {
height: 1,
},
counter: {
fontSize: 16,
marginBottom: 12,
fontStyle: 'italic',
},
hostButton: {
backgroundColor: '#007aff',
paddingVertical: 14,
borderRadius: 8,
alignItems: 'center',
},
buttonText: {
color: '#fff',
fontWeight: '600',
fontSize: 16,
},
sandboxSection: {
padding: 16,
borderTopWidth: 1,
borderTopColor: '#ccc',
},
sandbox: {
height: 400,
borderWidth: 1,
borderColor: '#8232ff',
borderRadius: 4,
},
})
video-demo-20.mp4
With the # of views changed, I can then see it fire on the second button.
Some logs I added to back up the view tag notion described above
04-08 10:43:40.821 3588 3621 I ReactNativeJS: [REPRO] Host button nativeTag=20
04-08 10:43:44.408 3588 3588 D SandboxTouch: TOUCH_DOWN local=(808.0,153.0) raw=(853.0,821.0) viewOnScreen=(45,668) viewSize=(990x1044) myId=34 childCount=1
04-08 10:43:44.409 3588 3588 D SandboxTouch: child class=ReactSurfaceView childId=31
04-08 10:43:44.409 3588 3588 D SandboxTouch: child[0] class=ReactScrollView id=50
04-08 10:43:44.409 3588 3588 D SandboxTouch: child[0] class=ReactViewGroup id=48
04-08 10:43:44.409 3588 3588 D SandboxTouch: child[0] class=ReactTextView id=6
04-08 10:43:44.409 3588 3588 D SandboxTouch: child[1] class=ReactViewGroup id=12
04-08 10:43:44.409 3588 3588 D SandboxTouch: child[0] class=ReactTextView id=10
04-08 10:43:44.409 3588 3588 D SandboxTouch: child[2] class=ReactViewGroup id=20
04-08 10:43:44.409 3588 3588 D SandboxTouch: child[0] class=ReactTextView id=18
04-08 10:43:44.409 3588 3588 D SandboxTouch: child[3] class=ReactViewGroup id=28
04-08 10:43:44.409 3588 3588 D SandboxTouch: child[4] class=ReactViewGroup id=36
04-08 10:43:44.409 3588 3588 D SandboxTouch: child[5] class=ReactViewGroup id=46
I also did some testing with focus using textInput as well as accessibility testing which all seemed to work as expected. I'm thus far only see this tag resolution present an issue with touch events
Summary
When testing with the fs experiment demo (as of 5.0) on Android, I was seeing touch event seemingly firing twice. I was able to reproduce the issue after tweaking the basic demo slightly, see video below
video-demo-28.mp4
Analysis
This seems to be related to how fabric on android resolves view tags. Notably, to reproduce the demo, I added a bunch of empty padding views such that Host Button has tag 28 and the 3rd button in the sandbox also has tag 28.
Adding some AI-assisted analysis:
Reproduction
Was able to reproduce following changes to the basic demo:
App.tsx
CrashIfYouCanDemo.tsx
new TouchBleedRepo.tsx
video-demo-20.mp4
With the # of views changed, I can then see it fire on the second button.
Some logs I added to back up the view tag notion described above
I also did some testing with focus using textInput as well as accessibility testing which all seemed to work as expected. I'm thus far only see this tag resolution present an issue with touch events