Skip to content

Commit a6304b4

Browse files
authored
Fix: onPress callbacks are invoked for all nested Pressables (#3295)
## Description <!-- Description and motivation for this PR. Include 'Fixes #<number>' if this is fixing some issue. --> Fixes #3282 The [onStart](https://github.com/software-mansion/react-native-gesture-handler/blob/main/src/components/Pressable/Pressable.tsx#L309-L344) and [onBegin](https://github.com/software-mansion/react-native-gesture-handler/blob/main/src/components/Pressable/Pressable.tsx#L303-L308) callbacks are invoked on the deepest responder on iOS and Android respectively. Inside those methods the `isTouchPropagationAllowed` ref is set to true. Base on that we can assume which `Pressable` should be the responder of the touch on the JS side. Yet there is nothing that would stop invoking `onPress` callbacks if the `isTouchPropagationAllowed` is set to false in [pressOutHandler](https://github.com/software-mansion/react-native-gesture-handler/blob/main/src/components/Pressable/Pressable.tsx#L136-L176). The fix introduces early return in the `pressOutHandler` if clicked `Pressable` is not the deepest one. On the old architecture the [measure](https://github.com/software-mansion/react-native-gesture-handler/blob/main/src/components/Pressable/Pressable.tsx#L214-L255) method is called asynchronously, usually after the `onStart`. In nested pressables, if the deepest one is clicked it still calls `onTouchesDown` and `onTouchesUp`, which sets [shouldPreventNativeEffects](https://github.com/software-mansion/react-native-gesture-handler/blob/main/src/components/Pressable/Pressable.tsx#L265-L267) to `true`, omitting the `onStart`, which sets it back to `false`. Because of that, when now we tried to click on the outer Pressable it would be stopped in [onStart shouldPreventNativeEvents check](https://github.com/software-mansion/react-native-gesture-handler/blob/main/src/components/Pressable/Pressable.tsx#L338-L341) without setting the `isTouchPropagationAllowed` to `true` and this is the exact same state that led to setting the `shouldPreventNativeEffects` to `true` so it is a cycle. The simplest solution for this is to check if the `onStart` is called before the `measure` in `shouldPreventNativeEffects` check and early return if it's not. ## Test plan Click on the red box and notice that callbacks on its parent are not invoked. Click on the text input to see that the callbacks on its Pressable parent are also not invoked. <!-- Describe how did you test this change here. --> <details> <summary>Code</summary> ```ts import * as React from 'react'; import { View, Text, Pressable as RNPressable, SafeAreaView, StyleSheet, TextInput as RNTextInput, } from 'react-native'; import { GestureHandlerRootView, Pressable as RNGHPressable, TextInput as RNGHTextInput, } from 'react-native-gesture-handler'; export default function HomeScreen() { return ( <GestureHandlerRootView> <SafeAreaView style={{ flex: 1 }}> <View style={styles.container}> <View style={styles.wrapper}> <RNGHPressable style={[styles.pressableStyles, styles.outerPressableStyles]} onPress={() => console.log(JSON.stringify('RNGH: Parent Press'))} onPressIn={() => console.log('RNGH: Parent PressIn')} onPressOut={() => console.log('RNGH: Parent PressOut')}> <RNGHPressable style={[styles.pressableStyles, styles.innerPressableStyles]} onPress={() => console.log(JSON.stringify('RNGH: Child Press'))} onPressIn={() => console.log('RNGH: Child PressIn')} onPressOut={() => console.log('RNGH: Child PressOut')}> <Text>RNGH</Text> </RNGHPressable> </RNGHPressable> <RNPressable style={[styles.pressableStyles, styles.outerPressableStyles]} onPress={() => console.log(JSON.stringify('RN: Parent Press'))} onPressIn={() => console.log('RN: Parent PressIn')} onPressOut={() => console.log('RN: Parent PressOut')}> <RNPressable style={[styles.pressableStyles, styles.innerPressableStyles]} onPress={() => console.log(JSON.stringify('RN: Child Press'))} onPressIn={() => console.log('RN: Child PressIn')} onPressOut={() => console.log('RN: Child PressOut')}> <Text>RN</Text> </RNPressable> </RNPressable> </View> <View style={styles.wrapper}> <RNGHPressable style={styles.textInputPressable} onPress={() => console.log(JSON.stringify('RNGH: TextInput Press')) } onPressIn={() => console.log('RNGH: TextInput PressIn')} onPressOut={() => console.log('RNGH: TextInput PressOut')}> <RNGHTextInput placeholder="RNGH TextInput" style={styles.textInputStyles} onChangeText={(text) => console.log(text)} /> </RNGHPressable> <RNPressable style={styles.textInputPressable} onPress={() => console.log(JSON.stringify('RN: TextInput Press'))} onPressIn={() => console.log('RN: TextInput PressIn')} onPressOut={() => console.log('RN: TextInput PressOut')}> <RNTextInput placeholder="RN TextInput" style={styles.textInputStyles} onChangeText={(text) => console.log(text)} /> </RNPressable> </View> </View> </SafeAreaView> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, wrapper: { alignItems: 'center', justifyContent: 'center', gap: 10, flexDirection: 'row', marginTop: 10, }, pressableStyles: { paddingVertical: 15, paddingHorizontal: 20, alignItems: 'center', justifyContent: 'center', }, innerPressableStyles: { backgroundColor: 'red', width: 120, height: 50, }, outerPressableStyles: { backgroundColor: '#3BE7BB', }, textInputPressable: { backgroundColor: 'gray', padding: 10, borderRadius: 5, width: 160, // height: 50, alignItems: 'center', justifyContent: 'center', }, textInputStyles: { backgroundColor: 'lightgray', paddingHorizontal: 20, paddingVertical: 10, height: 40 }, pressableText: { color: '#000', fontSize: 16, fontWeight: 'bold', }, }); ``` </details>
1 parent f12d9f6 commit a6304b4

File tree

2 files changed

+43
-4
lines changed

2 files changed

+43
-4
lines changed

example/src/release_tests/nestedPressables/index.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,28 @@ const outerStyle = ({ pressed }: PressableStateCallbackType) => [
5050

5151
function GesturizedBoxes() {
5252
return (
53-
<GesturizedPressable style={outerStyle} testID="outer">
54-
<GesturizedPressable style={middleStyle} testID="middle">
55-
<GesturizedPressable style={innerStyle} testID="inner" />
53+
<GesturizedPressable
54+
style={outerStyle}
55+
testID="outer"
56+
onPressIn={() => console.log('[outer] onPressIn')}
57+
onPressOut={() => console.log('[outer] onPressOut')}
58+
onPress={() => console.log('[outer] onPress')}
59+
onLongPress={() => console.log('[outer] onLongPress')}>
60+
<GesturizedPressable
61+
style={middleStyle}
62+
testID="middle"
63+
onPressIn={() => console.log('[middle] onPressIn')}
64+
onPressOut={() => console.log('[middle] onPressOut')}
65+
onPress={() => console.log('[middle] onPress')}
66+
onLongPress={() => console.log('[middle] onLongPress')}>
67+
<GesturizedPressable
68+
style={innerStyle}
69+
testID="inner"
70+
onPressIn={() => console.log('[inner] onPressIn')}
71+
onPressOut={() => console.log('[inner] onPressOut')}
72+
onPress={() => console.log('[inner] onPress')}
73+
onLongPress={() => console.log('[inner] onLongPress')}
74+
/>
5675
</GesturizedPressable>
5776
</GesturizedPressable>
5877
);

src/components/Pressable/Pressable.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,24 @@ export default function Pressable(props: PressableProps) {
138138

139139
const pressOutHandler = useCallback(
140140
(event: PressableEvent) => {
141+
if (!isTouchPropagationAllowed.current) {
142+
hasPassedBoundsChecks.current = false;
143+
isPressCallbackEnabled.current = true;
144+
deferredEventPayload.current = null;
145+
146+
if (longPressTimeoutRef.current) {
147+
clearTimeout(longPressTimeoutRef.current);
148+
longPressTimeoutRef.current = null;
149+
}
150+
151+
if (pressDelayTimeoutRef.current) {
152+
clearTimeout(pressDelayTimeoutRef.current);
153+
pressDelayTimeoutRef.current = null;
154+
}
155+
156+
return;
157+
}
158+
141159
if (
142160
!hasPassedBoundsChecks.current ||
143161
event.nativeEvent.touches.length >
@@ -340,7 +358,9 @@ export default function Pressable(props: PressableProps) {
340358

341359
if (shouldPreventNativeEffects.current) {
342360
shouldPreventNativeEffects.current = false;
343-
return;
361+
if (!handlingOnTouchesDown.current) {
362+
return;
363+
}
344364
}
345365

346366
isTouchPropagationAllowed.current = true;

0 commit comments

Comments
 (0)