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:
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>
);
}
Carousel with Dismiss
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>
);
}
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.