question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

waitFor and simultaneousHandlers do not work with LongPressGestureHandler and PanGestureHandler

See original GitHub issue

Description

Adding the option that PanGestureHandler waitFor a LongPressGestureHandler does not work, the PanGestureHandler activates directly. And once LongPressGestureHandler activates, PanGestureHandler deactivates even with simultanousHandler setup according to docs.

Let me know if I have done anything incorrectly, but I have only followed the docs to the best of my ability combining waitFor and simultaneousHandlers…

Screenshots

Steps To Reproduce

  1. Create a new blank project with expo (expo init)
  2. Install Reanimated 2 and Gesture handler (expo install react-native-gesture-handler && npm install react-native-reanimated@2.0.0-rc.0)
  3. Copy and paste in the babel plugin for expo reanimated (plugins: ['react-native-reanimated/plugin'],)
  4. Download any image to project-dir/assets/example.jpg
  5. Copy and paste the below code snippet into the App.js and start the expo dev client (expo start -c)
  6. Try and drag the image and longpress - you will see it is reversed from what is expected.

Expected behavior

The PanGestureHandler should waitFor the LongPressGestureHandler to exit the “BEGIN” state before activating, and stay activated once the LongPressGestureHandler activates according to simultaneousHandlers.

Actual behavior

The PanGestureHandler ignores waitFor and simultaneousHandler and activates before LongPressGestureHandler, and deactives once LongPressGestureHandler activates.

Snack or minimal code example

import React from "react";
import { View, Image } from "react-native";
import {
  PanGestureHandler,
  LongPressGestureHandler,
  ScrollView,
} from "react-native-gesture-handler";

import Animated, {
  useAnimatedStyle,
  useAnimatedGestureHandler,
  useSharedValue,
} from "react-native-reanimated";

export default function App() {
  const imageSize = 50;

  const longPressRef = React.createRef();
  const panRef = React.createRef();

  const y = useSharedValue(0);
  const x = useSharedValue(0);
  const onGestureEvent = useAnimatedGestureHandler({
    onStart: (event, ctx) => {
      ctx.offsetX = x.value;
    },
    onActive: (event, ctx) => {
      y.value = event.translationY;
      x.value = event.translationX + ctx.offsetX;
    },
  });

  const style = useAnimatedStyle(() => ({
    position: "absolute",
    top: 0,
    left: 0,
    width: imageSize,
    height: imageSize,
    transform: [{ translateX: x.value }, { translateY: y.value }],
  }));

  return (
    <View
      style={{
        flex: 1,
        paddingTop: 100,
        backgroundColor: "#fff",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      <ScrollView
        horizontal={true}
        showsHorizontalScrollIndicator={false}
        style={{ positon: "relative", height: imageSize, borderWidth: 1 }}
        contentContainerStyle={{ width: imageSize * 3 }}
      >
        <PanGestureHandler
          ref={panRef}
          simultaneousHandlers={longPressRef}
          waitFor={longPressRef}
          onHandlerStateChange={({ nativeEvent }) =>
            console.log("PANGESTURE ", nativeEvent.state)
          }
          {...{ onGestureEvent }}
        >
          <Animated.View>
            <LongPressGestureHandler
              ref={longPressRef}
              minDurationMs={1000}
              simultaneousHandlers={panRef}
              onHandlerStateChange={({ nativeEvent }) => {
                console.log("LONG PRESS", nativeEvent.state);
                console.log("LONG PRESS, PAN GESTURE SHOULD NOW ACTIVATE");
              }}
            >
              <Animated.View
                style={[
                  {
                    position: "absolute",
                    top: 0,
                    left: 0,
                  },
                  style,
                ]}
              >
                <Image
                  source={require("./assets/example.jpg")}
                  style={{ width: imageSize, height: imageSize }}
                />
              </Animated.View>
            </LongPressGestureHandler>
          </Animated.View>
        </PanGestureHandler>
      </ScrollView>
    </View>
  );
}

Package versions

"expo": "~40.0.0",
"react-native-reanimated": "^2.0.0-rc.0",
"react-native-gesture-handler": "~1.8.0"

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:2
  • Comments:14 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
aLemonFoxcommented, Oct 3, 2021

For anyone coming across something like this in the future, this is how I solved it:

  1. Make a ScrollView component and set its scrollEnabled to your trigger value:
    const scrollViewRef = useRef(null);
    const [allowedDragId, setAllowedDragId] = useState<string | null>(null);

    const handleDragStart = (id: string) => {
        setAllowedDragId(id);
    }
    const handleDragEnd = () => {
        setAllowedDragId(null);
    }

    return (
        <ScrollView
            ref={scrollViewRef}
            scrollEnabled={allowedDragId === null}
        >
            {LOCATIONS.map((location, index) => (
                <DraggableView
                    scrollViewRef={scrollViewRef}
                    onDragStart={handleDragStart}
                    onDragEnd={handleDragEnd}
                    allowedDragId={allowedDragId}
                />
            ))}
        </ScrollView>
    );
  1. In your draggable component, call onDragStart() when you want to allow a component to be draggable. In the gestureHandler, filter on the trigger value from the previous step:
    const [isDragging, setDragging] = useState(false);

    const eventHandler = useAnimatedGestureHandler({
        onStart: (event, ctx) => {
            if (props.allowedDragId === props.location.id) {
                runOnJS(setDragging)(true);
            }
        },
        onActive: (event, ctx) => {
            if (isDragging) {
            // dragging code
        },
        onEnd: (event, ctx) => {
            if (isDragging) {
                // drag end code
                runOnJS(setDragging)(false);
                runOnJS(props.onDragEnd)();
            }
        },
    });

    return (
        <Animated.View style={animatedStyle}>
            <PanGestureHandler
                onGestureEvent={eventHandler}
                simultaneousHandlers={props.scrollViewRef}
            >
                <Animated.View>
                    <CustomView onLongPress={() => props.onDragStart(props.location.id)} />
                </Animated.View>
            </PanGestureHandler>
        </Animated.View>
    );
  1. Pass scrollViewRef to the PanGestureHandler simultaneousHandlers prop.

You can now enable and disable dragging your draggable component without having to lift your finger off the screen.

1reaction
Svartocommented, Jan 13, 2021

@MoOx hmm, damn it just doesn’t work for me. Once LongPressGestureHandler activates, then the PanGestureHandler deactivates and stops working. Somehow the PanGestureHandler also activates before LongPressGestureHandler (i.e. ignores waitFor). It is so weird…

This is the code I am using now, replicating what you just detailed out.

import React from "react";
import { View, Image } from "react-native";
import {
  PanGestureHandler,
  LongPressGestureHandler,
  ScrollView,
} from "react-native-gesture-handler";

import Animated, {
  useAnimatedStyle,
  useAnimatedGestureHandler,
  useSharedValue,
} from "react-native-reanimated";

export default function App() {
  const imageSize = 50;

  const scrollRef = React.useRef(null);
  const longPressRef = React.useRef(null);
  const panRef = React.useRef(null);

  const y = useSharedValue(0);
  const x = useSharedValue(0);
  const onGestureEvent = useAnimatedGestureHandler({
    onStart: (event, ctx) => {
      ctx.offsetX = x.value;
    },
    onActive: (event, ctx) => {
      y.value = event.translationY;
      x.value = event.translationX + ctx.offsetX;
    },
  });

  const style = useAnimatedStyle(() => ({
    position: "absolute",
    top: 0,
    left: 0,
    width: imageSize,
    height: imageSize,
    transform: [{ translateX: x.value }, { translateY: y.value }],
  }));

  return (
    <View
      style={{
        flex: 1,
        paddingTop: 100,
        height: 300,
        backgroundColor: "#fff",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      <ScrollView
        ref={scrollRef}
        horizontal={true}
        showsHorizontalScrollIndicator={false}
        style={{ positon: "relative", height: imageSize, borderWidth: 1 }}
        contentContainerStyle={{ width: imageSize * 3 }}
      >
        <PanGestureHandler
          ref={panRef}
          simultaneousHandlers={scrollRef}
          waitFor={longPressRef}
          onHandlerStateChange={({ nativeEvent }) =>
            console.log("PANGESTURE ", nativeEvent.state)
          }
          {...{ onGestureEvent }}
        >
          <Animated.View>
            <LongPressGestureHandler
              ref={longPressRef}
              minDurationMs={0}
              simultaneousHandlers={panRef}
              onHandlerStateChange={({ nativeEvent }) => {
                console.log("LONG PRESS", nativeEvent.state);
                console.log("LONG PRESS, PAN GESTURE SHOULD NOW ACTIVATE");
              }}
            >
              <Animated.View
                style={[
                  {
                    position: "absolute",
                    top: 0,
                    left: 0,
                  },
                  style,
                ]}
              >
                <Image
                  source={require("./assets/example.jpg")}
                  style={{ width: imageSize, height: imageSize }}
                />
              </Animated.View>
            </LongPressGestureHandler>
          </Animated.View>
        </PanGestureHandler>
      </ScrollView>
    </View>
  );
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

waitFor and simultaneousHandlers do not work with ...
waitFor and simultaneousHandlers do not work with LongPressGestureHandler and PanGestureHandler.
Read more >
Activate the PanGestureHandler after a long press delay ...
As the title of the question says, I want to activate the PanGestureHandler after a long press delay let's say a second (1000...
Read more >
Common handler properties | React Native Gesture Handler
waitFor ​ ... Accepts a react ref object or an array of refs to other handler components (refs should be created using React.createRef()...
Read more >
brentvatne/react-native-gesture-handler - npm package
Looks like @brentvatne/react-native-gesture-handler is missing a security ... it with waitFor or simultaneousHandlers props in other handler component.
Read more >
react-native-gesture-handler - npm
There are 1015 other projects in the npm registry using ... until it knows pan won't recognize; You can use touchables that run...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found