Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix handlers coming back from being cancelled (#2704)
## Description This is loosely related to #2693, as debugging it allowed to find this bug. Currently when a handler finishes while it's waiting, it only sends events if it fails or when it gets canceled. If the gesture finishes successfully, the event is not sent as it's waiting for another one. If the gesture it's waiting for fails, it goes through all awaiting handlers and tries to activate them if criteria are met. Since the gesture it tries to activate is already finished, synthetic events need to be sent to the JS side to achieve the correct behavior. Because of that, if the gesture is cancelled while waiting and then the gesture it waited for fails, GH will try to activate the cancelled gesture and will send the synthetic events. So the events would look like this: 1. `UNDETERMINED` -> `BEGAN` 2. `BEGAN` -> `CANCELLED` 3. `BEGAN` -> `ACTIVE` 4. `ACTIVE` -> `ENDED` 5. `ENDED` -> `UNDETERMINED` I.e. in certain conditions, a gesture could be activated after it was canceled, this PR simply adds a condition that the gesture cannot be in `FAILED` or `CANCELLED` state before sending the synthetic events. ## Test plan <details> <summary>Tested on the following code in the Example app.</summary> ```jsx import React, { useRef } from 'react'; import { StyleSheet, Text, ScrollView, View } from 'react-native'; import { GestureDetector, Gesture, RectButton, GestureHandlerRootView, } from 'react-native-gesture-handler'; export const DATA = new Array(100).fill(0).map((_, index) => `Item ${index}`); function Button({ text, panRef }: any) { const [selected, setSelected] = React.useState(false); return ( <RectButton waitFor={panRef} style={[styles.selectableItem, selected && styles.selectedItem]} onHandlerStateChange={(e) => { console.log( 'State change', e.nativeEvent.oldState, e.nativeEvent.state ); }} onPress={() => { setSelected(!selected); console.log('Pressed', text); }}> <Text style={[styles.genreText, styles.horizontalMargin]}>{text}</Text> </RectButton> ); } export default function App() { const panRef = useRef<any>(null); const scrollGesture = Gesture.Native(); const pan = Gesture.Pan().manualActivation(true).withRef(panRef); return ( <GestureDetector gesture={pan}> <View style={{ flex: 1 }}> <GestureDetector gesture={scrollGesture}> <ScrollView> <GestureHandlerRootView> {DATA.map((text, index) => ( <Button key={index} text={text} panRef={panRef} /> ))} </GestureHandlerRootView> </ScrollView> </GestureDetector> </View> </GestureDetector> ); } export const styles = StyleSheet.create({ genreText: { fontSize: 18, fontWeight: '600', marginVertical: 12, }, selectableItem: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 8, }, horizontalMargin: { marginHorizontal: 16, }, selectedItem: { backgroundColor: '#aaa', } }); ``` </details> Before: https://github.com/software-mansion/react-native-gesture-handler/assets/21055725/35211c91-544b-4612-9e80-ee8abed9b646 After: https://github.com/software-mansion/react-native-gesture-handler/assets/21055725/c8b8da6e-aae8-4b09-acca-1286dab6b4fa --------- Co-authored-by: Michał Bert <[email protected]>
- Loading branch information