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

Gesture.Tap().maxDuration(...) not working on web

Open elliotwaite opened this issue 2 years ago • 2 comments

Description

Using maxDuration() on a Tap gesture doesn't seem to work on web.

Platforms

  • [ ] iOS
  • [ ] Android
  • [x] Web

Screenshots

screen-recording

Steps To Reproduce

Run the code example below on the web then tap and hold on the red circle.

Expected behavior

For it to behave the same as on native where if the tap is held longer than the maxDuration value, the onTouchesCancelled callback is called followed by the onFinalize callback being called.

Actual behavior

On web, the tap can be held forever without ever being canceled.

Snack or minimal code example

import { Platform, StyleSheet, Text } from 'react-native'
import {
  Gesture,
  GestureDetector,
  GestureHandlerRootView,
} from 'react-native-gesture-handler'
import Animated, {
  useAnimatedStyle,
  useSharedValue,
} from 'react-native-reanimated'

export default function App() {
  const isPressed = useSharedValue(false)
  const wasCancelled = useSharedValue(false)
  const wasFinalized = useSharedValue(false)

  const tapGesture = Gesture.Tap()
    .maxDuration(500)
    .onBegin(() => {
      isPressed.value = true
      wasCancelled.value = false
      wasFinalized.value = false
    })
    .onTouchesCancelled(() => {
      wasCancelled.value = true
    })
    .onFinalize(() => {
      isPressed.value = false
      wasFinalized.value = true
    })

  const wasCancelledAnimatedStyles = useAnimatedStyle(() => ({
    opacity: wasCancelled.value ? 1 : 0,
  }))

  const wasFinalizedAnimatedStyles = useAnimatedStyle(() => ({
    opacity: wasFinalized.value ? 1 : 0,
  }))

  const ballAnimatedStyles = useAnimatedStyle(() => ({
    backgroundColor: isPressed.value ? 'yellow' : 'red',
  }))

  return (
    <GestureHandlerRootView style={styles.container}>
      <Text style={styles.text}>
        {Platform.OS === 'web' ? 'Web Version' : 'Native Version'}
      </Text>
      <Animated.View style={wasCancelledAnimatedStyles}>
        <Text style={styles.text}>`onTouchesCancelled()` was called.</Text>
      </Animated.View>
      <Animated.View style={wasFinalizedAnimatedStyles}>
        <Text style={styles.text}>`onFinalize()` was called.</Text>
      </Animated.View>
      <GestureDetector gesture={tapGesture}>
        <Animated.View style={[styles.ball, ballAnimatedStyles]} />
      </GestureDetector>
    </GestureHandlerRootView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    paddingBottom: 16,
    fontSize: 16,
  },
  ball: {
    width: 128,
    height: 128,
    borderRadius: 64,
  },
})

Package versions

  • React: 17.0.1
  • React Native: 0.64.3
  • React Native Gesture Handler: 2.2.1
  • React Native Reanimated: 2.3.3

elliotwaite avatar May 18 '22 17:05 elliotwaite

It's similar situation to https://github.com/software-mansion/react-native-gesture-handler/issues/2063#issuecomment-1131475881. If you want to know whether the gesture ended successfully or was cancelled you can use the second argument of onEnd and onFinalize callbacks, for example:

const tap = Gesture.Tap()
  .onBegin(() => console.log('begin'))
  .onStart(() => console.log('start'))
  .onEnd((_e, success) => console.log('end, successful:', success))
  .onFinalize((_e, success) => console.log('finalize, successful:', success));

j-piasecki avatar May 19 '22 10:05 j-piasecki

Okay, good to know. Thanks!

elliotwaite avatar May 19 '22 14:05 elliotwaite

Hi 👋 This issue should be fixed in 2.6.1. What's left is that onTouchesCancelled callback is not being triggered. You can try this PR and everything should work fine.

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 11:09 m-bert

Hey! 👋

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?

github-actions[bot] avatar Sep 22 '22 11:09 github-actions[bot]

@Warus15, thanks for letting me know. I just got around to testing out the latest version (2.7.0) with enableExperimentalWebImplementation(true), and I can confirm that it now works as expected on the web, so I'll close this issue.

However, I did notice that a holding a tap longer than the the max duration results in a slight difference in behavior between the platforms:

On Android and web, the callbacks called are:

  • onBegin
  • onTouchesCancelled
  • onFinalize

On iOS, the callbacks called are:

  • onBegin
  • onTouchesCancelled
  • onFinalize
  • onFinalize (called a second time)

The second call to onFinalize on iOS only occurs when the max duration is exceeded. It doesn't occur if the tap is released before reaching the max duration (in which case it's only called a single time all on platforms). Do think this difference in behavior should be fixed? If so, I can open a separate issue about this.

elliotwaite avatar Oct 07 '22 06:10 elliotwaite

Hi @elliotwaite! I'm happy to hear that new implementation solves this issue. Also it would be nice if you could open issue for this double onFinalize callback, because with repro it would be easier to see whether that's something that we should worry about.

m-bert avatar Oct 07 '22 09:10 m-bert

Okay, sounds good, I created the new issue here: https://github.com/software-mansion/react-native-gesture-handler/issues/2263

elliotwaite avatar Oct 07 '22 13:10 elliotwaite