react-nativegesturepanresponder

React Native PanResponder onStartShouldSetPanResponder locationX varies by platform


I'm new to RN gesture apis, so if any of this looks like a fundamentally incorrect approach, I would really appreciate the feedback as well as any suggestions on where to look instead. Thanks! 🙏

Goals

The following code receives a different event.nativeEvent according to whether I am testing via expo's local web server and expo ios app. This seems reasonable, given how different the underlying infrastructures are.

My hunch is that on ios, the onStartShouldSetPanResponder handler is firing 'too late' (or too early?), that is, the gesture is being attributed to a child component, so the event bubbling up to this handler has positioning relative to the child component, not the list component in the render function below.

const panResponder = useRef(
  PanResponder.create({
    onStartShouldSetPanResponder: (event: GestureResponderEvent) => {
      console.log(event.nativeEvent.locationX)
      updateSelectionValue(event.nativeEvent.locationX) // does some math and conditionally updates local state
    },
    onStartShouldSetPanResponder: () => true,
    onPanResponderMove: handleSwipe,
  })
).current

return (
  <View onLayout={handleLayout} {...panResponder.panHandlers}>
    <ListComponent proportion={selectionValue} {...props} />
  </View>
)

Given that the list component is 300px wide, and there are 3 100px wide items in the list component, tapping approximately 5/6 of the width from left side of the list component yields a locationX of ~250/300 (correct) on web and 50/300 on ios.

List     [----------------300px----------------]
Children [---100px---][---100px---][---100px---]
Tapping Here ---------------------------^^

It seems that on ios, the event is positioned relative to the 3rd 100px wide child element.

Additional Context:

node --version
v15.12.0

"dependencies": {
  "expo": "~40.0.0",
  "expo-status-bar": "~1.0.3",
  "react": "16.13.1",
  "react-dom": "16.13.1",
  "react-native": "https://github.com/expo/react-native/archive/sdk-40.0.1.tar.gz",
  "react-native-web": "~0.13.12"
},

Solution

  • ended up setting a ref onLayout and ignoring event.nativeEvent in favor of gestureState.x0 and gestureState.moveX

    let coords = useRef({ x: -1, y: -1 }).current
    
    const handleLayout = useCallback((event: LayoutChangeEvent) => {
      const { x, y } = event.nativeEvent.layout
      coords = { x: Math.round(x), y: Math.round(y) }
    }, [])
    ...
    const panResponder = useRef(
     PanResponder.create({
       onStartShouldSetPanResponder: () => true,
       onPanResponderStart: (event, gestureState) =>
       updateSelectionValue(gestureState.x0 - coords.x),
       onPanResponderMove: (event, gestureState) =>
       updateSelectionValue(gestureState.moveX - coords.x),
      })
    ).current