Skip to main content

Overview

The useScreenGesture hook returns a reference to the screen’s navigation pan gesture. Use this hook to coordinate your own pan gestures with the navigation dismiss gesture, ensuring they work together smoothly without conflicts.

Import

import { useScreenGesture } from 'react-native-screen-transitions';

Usage

import { useScreenGesture } from 'react-native-screen-transitions';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { View } from 'react-native';

function MyScreen() {
  const screenGesture = useScreenGesture();

  const myPanGesture = Gesture.Pan()
    .simultaneousWithExternalGesture(screenGesture)
    .onUpdate((e) => {
      // Your custom gesture logic
      console.log('Pan translation:', e.translationX, e.translationY);
    });

  return (
    <GestureDetector gesture={myPanGesture}>
      <View style={{ flex: 1 }}>
        {/* Your content */}
      </View>
    </GestureDetector>
  );
}

Return Value

Returns a gesture ref that can be used with React Native Gesture Handler’s gesture composition methods:
screenGesture
Ref | null
Reference to the screen’s pan gesture handler. Returns null if no gesture is available (e.g., gestures are disabled).

Gesture Composition Methods

Use these methods from react-native-gesture-handler to coordinate your gestures:
  • simultaneousWithExternalGesture() - Both gestures can be active at the same time
  • waitFor() - Your gesture waits for the screen gesture to fail before activating
  • requireExternalGestureToFail() - Your gesture requires the screen gesture to fail
  • blocksExternalGesture() - Your gesture blocks the screen gesture

Examples

Simultaneous Gestures

Allow both your custom gesture and the dismiss gesture to work together:
import { useScreenGesture } from 'react-native-screen-transitions';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { useSharedValue } from 'react-native-reanimated';

function MyScreen() {
  const screenGesture = useScreenGesture();
  const translateX = useSharedValue(0);

  const panGesture = Gesture.Pan()
    .simultaneousWithExternalGesture(screenGesture)
    .onUpdate((e) => {
      translateX.value = e.translationX;
    })
    .onEnd(() => {
      translateX.value = withSpring(0);
    });

  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View style={{ flex: 1 }}>
        {/* Draggable content */}
      </Animated.View>
    </GestureDetector>
  );
}

Wait for Navigation Gesture

Make your gesture wait for the navigation gesture to fail first:
import { useScreenGesture } from 'react-native-screen-transitions';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

function SwipeableCard() {
  const screenGesture = useScreenGesture();
  const offset = useSharedValue(0);

  const swipeGesture = Gesture.Pan()
    .waitFor(screenGesture) // Wait for dismiss gesture to fail
    .activeOffsetX([-20, 20]) // Only activate with horizontal swipe
    .onUpdate((e) => {
      offset.value = e.translationX;
    })
    .onEnd((e) => {
      if (Math.abs(e.translationX) > 100) {
        // Swipe threshold reached
        offset.value = withSpring(e.translationX > 0 ? 300 : -300);
      } else {
        offset.value = withSpring(0);
      }
    });

  return (
    <GestureDetector gesture={swipeGesture}>
      <Animated.View style={{ transform: [{ translateX: offset }] }}>
        <Card />
      </Animated.View>
    </GestureDetector>
  );
}

Horizontal Swiper with Vertical Dismiss

import { useScreenGesture } from 'react-native-screen-transitions';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

function ImageSwiper({ images }) {
  const screenGesture = useScreenGesture();
  const translateX = useSharedValue(0);
  const currentIndex = useSharedValue(0);

  const swipeGesture = Gesture.Pan()
    .simultaneousWithExternalGesture(screenGesture)
    .activeOffsetX([-20, 20]) // Horizontal swipes
    .failOffsetY([-20, 20]) // Fail on vertical swipes (let dismiss gesture handle it)
    .onUpdate((e) => {
      translateX.value = e.translationX - currentIndex.value * SCREEN_WIDTH;
    })
    .onEnd((e) => {
      const shouldGoNext = e.translationX < -50 && e.velocityX < -500;
      const shouldGoPrev = e.translationX > 50 && e.velocityX > 500;

      if (shouldGoNext && currentIndex.value < images.length - 1) {
        currentIndex.value += 1;
      } else if (shouldGoPrev && currentIndex.value > 0) {
        currentIndex.value -= 1;
      }

      translateX.value = withSpring(-currentIndex.value * SCREEN_WIDTH);
    });

  return (
    <GestureDetector gesture={swipeGesture}>
      <Animated.View>
        {images.map((image, index) => (
          <Image key={index} source={image} />
        ))}
      </Animated.View>
    </GestureDetector>
  );
}

Block Navigation Gesture

Prevent the navigation dismiss gesture in certain areas:
import { useScreenGesture } from 'react-native-screen-transitions';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

function DrawingCanvas() {
  const screenGesture = useScreenGesture();
  const path = useSharedValue([]);

  const drawGesture = Gesture.Pan()
    .blocksExternalGesture(screenGesture) // Block dismiss while drawing
    .onStart((e) => {
      path.value = [{ x: e.x, y: e.y }];
    })
    .onUpdate((e) => {
      path.value = [...path.value, { x: e.x, y: e.y }];
    })
    .onEnd(() => {
      // Save drawing
    });

  return (
    <View style={{ flex: 1 }}>
      <GestureDetector gesture={drawGesture}>
        <Canvas path={path} />
      </GestureDetector>
    </View>
  );
}
import { useScreenGesture } from 'react-native-screen-transitions';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

function CarouselScreen() {
  const screenGesture = useScreenGesture();
  const activeIndex = useSharedValue(0);

  const panGesture = Gesture.Pan()
    .simultaneousWithExternalGesture(screenGesture)
    .onUpdate((e) => {
      const isHorizontal = Math.abs(e.translationX) > Math.abs(e.translationY);
      
      if (isHorizontal) {
        // Handle horizontal carousel swipe
        // screenGesture will not activate
      }
      // Vertical swipes activate dismiss gesture automatically
    });

  return (
    <GestureDetector gesture={panGesture}>
      <View>
        {/* Carousel items */}
      </View>
    </GestureDetector>
  );
}

Nested Scrollable with Custom Gesture

import { useScreenGesture } from 'react-native-screen-transitions';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Transition from 'react-native-screen-transitions';

function BottomSheetWithDragHandle() {
  const screenGesture = useScreenGesture();

  const dragHandleGesture = Gesture.Pan()
    .simultaneousWithExternalGesture(screenGesture)
    .onUpdate((e) => {
      // Handle drag from handle area
      // Works together with sheet dismiss/snap gestures
    });

  return (
    <View style={{ flex: 1 }}>
      {/* Drag handle at top */}
      <GestureDetector gesture={dragHandleGesture}>
        <View style={{ height: 40, backgroundColor: '#ddd' }}>
          <View style={{ width: 40, height: 4, backgroundColor: '#999' }} />
        </View>
      </GestureDetector>
      
      {/* Scrollable content */}
      <Transition.ScrollView>
        {/* Content */}
      </Transition.ScrollView>
    </View>
  );
}

Notes

Always check if screenGesture is not null before using it. The gesture may be unavailable if gesture navigation is disabled or not configured.
When using simultaneousWithExternalGesture(), both gestures can be active at once. Make sure your gesture logic accounts for this, especially with directional gestures.
Use activeOffsetX / activeOffsetY and failOffsetX / failOffsetY to control gesture activation based on direction. This is crucial for avoiding conflicts between horizontal and vertical gestures.