On web, child and parent handler active simultaneously
Description
On the web, using React Native Web, a child PanGestureHandler does not prevent a parent PanGestureHandler from becoming active. Instead, they both proceed to active state. Perhaps I have misunderstood, I thought only one handler could be active at any given time?
Expected behavior
In the code example, it should be possible to move the small gold-colored view without the underlying red view moving.
Actual behavior
When dragging the gold-colored view, both its and its parents PanGestureHandlers become active, and the movement doubles.
Snack or minimal code example
const parentPos = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
const childPos = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
function onParentEvent(e) {
parentPos.setValue({x: 0, y: 0});
parentPos.setOffset({x: e.nativeEvent.translationX, y: e.nativeEvent.translationY});
}
function onChildEvent(e) {
childPos.setValue({x: 0, y: 0});
childPos.setOffset({x: e.nativeEvent.translationX, y: e.nativeEvent.translationY});
}
return(
<PanGestureHandler
onHandlerStateChange={(e) => console.log('parent state ' + e.nativeEvent.state)}
onGestureEvent={onParentEvent}
>
<Animated.View style={{position: 'absolute', left: parentPos.x, top: parentPos.y, width: 100, height: 100, backgroundColor: 'tomato'}}>
<PanGestureHandler
onHandlerStateChange={(e) => console.log('child state ' + e.nativeEvent.state)}
onGestureEvent={onChildEvent}
>
<Animated.View style={{position: 'absolute', left: childPos.x, top: childPos.y, width: 20, height: 20, backgroundColor: 'gold'}}/>
</PanGestureHandler>
</Animated.View>
</PanGestureHandler>
)
Package versions
- React: 17.0.1
- React Native Web: 0.14.7
- React Native Gesture Handler: 1.8.0
Could you please post the entire code so I can copy-paste and check it out locally?
Thanks much! This is a minimal index.js (CRA, react-app-rewired) to show the issue:
import React, { useRef } from 'react';
import ReactDOM from 'react-dom';
import { Animated } from 'react-native';
import { PanGestureHandler } from 'react-native-gesture-handler'
function App() {
const parentPos = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
const childPos = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
function onParentEvent(e) {
parentPos.setValue({x: 0, y: 0});
parentPos.setOffset({x: e.nativeEvent.translationX, y: e.nativeEvent.translationY});
}
function onChildEvent(e) {
childPos.setValue({x: 0, y: 0});
childPos.setOffset({x: e.nativeEvent.translationX, y: e.nativeEvent.translationY});
}
return(
<PanGestureHandler
onHandlerStateChange={(e) => console.log('parent state ' + e.nativeEvent.state)}
onGestureEvent={onParentEvent}
>
<Animated.View style={{position: 'absolute', left: parentPos.x, top: parentPos.y, width: 100, height: 100, backgroundColor: 'tomato'}}>
<PanGestureHandler
onHandlerStateChange={(e) => console.log('child state ' + e.nativeEvent.state)}
onGestureEvent={onChildEvent}
>
<Animated.View style={{position: 'absolute', left: childPos.x, top: childPos.y, width: 20, height: 20, backgroundColor: 'gold'}}/>
</PanGestureHandler>
</Animated.View>
</PanGestureHandler>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Workaround
I ran into this as well. For anyone looking for a relatively clean workaround - if you stop propagation on the "pressdown" event (the event that the underlying @egjs/hammerjs web gesture library uses to detect the start of a gesture) on the inner handler then the outer handler never activates. I created a wrapper component around PanGestureHandler that includes this behavior out of the box for the web:
// pan-gesture-handler-no-propagation.web.tsx
import React, { useRef, useEffect, ReactNode } from "react"
import {
PanGestureHandler,
PanGestureHandlerProps,
} from "react-native-gesture-handler"
const stopPropagation = (event: PointerEvent) => event.stopPropagation()
function PanGestureHandlerNoPropagation(
props: PanGestureHandlerProps & { children: ReactNode },
): JSX.Element {
const handlerRef = useRef<{ viewTag: HTMLElement }>()
useEffect(() => {
const handler = handlerRef.current
handler?.viewTag.addEventListener("pointerdown", stopPropagation)
return () =>
handler?.viewTag.removeEventListener("pointerdown", stopPropagation)
}, [])
return <PanGestureHandler {...props} ref={handlerRef} />
}
export default PanGestureHandlerNoPropagation
Hi, this issue should be fixed in 2.6.1.
Note that this change is available in new web implementation. To enable it, call enableExperimentalWebImplementation() in the root of your project.
I'll close the issue since the new web implementation is enabled by default starting with 2.10.0.