Search code examples
react-nativereact-forwardrefreact-native-reanimated-v2

Pass useAnimatedGestureHandler via forwardRef


I'm about to swap the old React Native Animated library with the new React Native Reanimated one to gain performance issues but I have encountered one problem I could not solve.

In all examples I found online, I saw that the GestureHandler, created with useAnimatedGestureHandler, is in the same component as the Animated.View. In reality that is sometimes not possible.

In my previous app, I just pass the GestureHandler object to the component via forwardRef but it seems React Native Reanimated is not able to do that. I don't know whether I have a syntax error or it is just a bug.

const App = () => {
  const handlerRef = useAnimatedRef();
  const y = useSharedValue(0);

  handlerRef.current = useAnimatedGestureHandler({
    onStart: (_, ctx) => {
      ctx.startY = y.value;
    },
    onActive: ({translationX, translationY}, ctx) => {
      y.value = translationY;
    },
    onEnd: () => {},
  });

  const animatedStyles = useAnimatedStyle(() => ({transform: [{translateY: withSpring(y.value)}]}));

  const UsingHandlerDirect = () => (
    <PanGestureHandler onGestureEvent={handlerRef.current} >
      <Animated.View style={[styles.blueBox, animatedStyles]} />
    </PanGestureHandler>
  )

  const UsingHandlerForwardRef = forwardRef(({animatedStyles}, ref) => (
    <PanGestureHandler onGestureEvent={ref?.handlerRef?.current}>
      <Animated.View style={[styles.redBox, animatedStyles]} />
    </PanGestureHandler>
  ));

  return (
    <SafeAreaView>
      <View style={styles.container}>
        <UsingHandlerForwardRef ref={handlerRef} animatedStyles={animatedStyles}/>
        <UsingHandlerDirect />
      </View>
    </SafeAreaView>
  );
}

I have saved the GestureHandler in a useAnimatedRef handlerRef.current = useAnimatedGestureHandler({}) to make things more representable. Then I pass the the ref directly into the PanGestureHandler of the UsingHandlerDirect component. The result is that when I drag the blue box the box will follow the handler. So this version works.

But as soon as I pass the handlerRef to the UsingHandlerForwardRef component non of the gesture events get fired. I would expect that when I drag the red box will also follow the handler but it doesn't

Has someone an idea whether it's me or it's a bug in the library?

Cheers


Solution

  • I have given up on the idea to pass a ref around instead, I created a hook that connects both components with each other via context.

    I created a simple hook

    
    import { useSharedValue } from 'react-native-reanimated';
    
    const useAppState = () => {
      const sharedXValue = useSharedValue(0);
    
      return {
        sharedXValue,
      };
    };
    
    export default useAppState;
    

    that holds the shared value using useSharedValue from reanimated 2

    The child component uses this value in the gestureHandler like that

    
    const gestureHandler = useAnimatedGestureHandler({
        onStart: (_, ctx) => {
          ctx.startX = sharedXValue.value;
        },
        onActive: (event, ctx) => {
          sharedXValue.value = ctx.startX + event.translationX;
        },
        onEnd: (_) => {
          sharedXValue.value = withSpring(0);
        },
      });
    

    and the Parent just consumes the hook value

    const animatedStyle = useAnimatedStyle(() => {
        return {
          transform: [
            {
              translateX: -sharedXValue.value,
            },
          ],
        };
      });
    

    I have created a workable Snack which contains the 2 components - a Child with a blue box and a Parent with a red box