react-native-gesture-handler icon indicating copy to clipboard operation
react-native-gesture-handler copied to clipboard

On web, child and parent handler active simultaneously

Open staffan-klashed opened this issue 5 years ago • 4 comments

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

staffan-klashed avatar Nov 24 '20 20:11 staffan-klashed

Could you please post the entire code so I can copy-paste and check it out locally?

jgonet avatar Nov 24 '20 21:11 jgonet

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')
);

staffan-klashed avatar Nov 24 '20 21:11 staffan-klashed

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

elliotdickison avatar May 20 '21 04:05 elliotdickison

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.

m-bert avatar Sep 22 '22 12:09 m-bert

I'll close the issue since the new web implementation is enabled by default starting with 2.10.0.

j-piasecki avatar May 17 '23 10:05 j-piasecki