In React Native Gesture Handler 3, what do GestureHandlerRootView and GestureDetector each do, and how do you attach a pan gesture to a view?
answer
- one root near the app entry
- a custom style replaces flex: 1
- hooks build the gesture config
- the detector wraps the target view
- one gesture instance per detector
basics
~20 sGestureHandlerRootView, placed near the app root, lets Gesture Handler intercept touches for everything inside it. You build a gesture with a hook such as usePanGesture and wrap the target view in a GestureDetector that receives it through its gesture prop.
solid answer
~40 s`GestureHandlerRootView` is the entry point: gestures are only recognised inside it, and relations only work between gestures under the same root, so it wraps the app or the navigator near the top. It behaves like a `View` whose default style is `{ flex: 1 }`; pass your own `style` and you must include `flex: 1` yourself, or nothing renders. On Android, a `Modal`'s content needs its own root view. In Gesture Handler 3 you create gestures with hooks such as `usePanGesture`, `useTapGesture` or `usePinchGesture`, passing callbacks and settings like `minDistance` in one config object. You then wrap the view in `<GestureDetector gesture={pan}>`. In development, a detector outside any root view throws. Use each gesture instance in one detector only, and do not nest detectors that mix the hook API with the older `Gesture.Pan()` builder.
code
tsx · 37 linesimport {StyleSheet, View} from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
usePanGesture,
} from 'react-native-gesture-handler';
function PhotoPage() {
const pan = usePanGesture({
minDistance: 10,
onActivate: () => {
console.log('pan activated');
},
onDeactivate: e => {
console.log('pan ended', e.canceled, e.translationX);
},
});
return (
<GestureDetector gesture={pan}>
<View style={styles.photo} />
</GestureDetector>
);
}
export default function App() {
return (
<GestureHandlerRootView style={styles.root}>
<PhotoPage />
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
root: {flex: 1},
photo: {flex: 1, backgroundColor: 'black'},
});go deeper
Recall the two pieces: a root view near the app entry and a GestureDetector around the view, fed by a hook such as usePanGesture.
Explain the root view's rules: default flex style, one root for related gestures, and the Android Modal case, plus one gesture per detector.
Show you have debugged setups: blank screens from a custom root style, silent gestures in modals or native-navigation screens, and detectors that break SVG hierarchies.
Decide where root views live in a large app and how shared components ship gestures so that teams do not rediscover these setup traps.
## What Gesture Handler does `react-native-gesture-handler` replaces React Native's JavaScript responder system for gestures with **native recognisers**. On iOS each gesture is backed by a `UIGestureRecognizer`; on Android, where the platform offers no equivalent, the library implements recognisers and an orchestrator that decides which gesture wins. Two components connect your React tree to that machinery: `GestureHandlerRootView` and `GestureDetector`. ## GestureHandlerRootView The root view is where the library starts intercepting touches. - **Place it as close to the app root as possible**, typically around the whole app or the navigator. Gestures outside it are not recognised. - **Relations only work under one root.** Two gestures that must know about each other have to live under the same `GestureHandlerRootView`. - **It behaves like a `View`.** With no `style` it defaults to `{ flex: 1 }`. A custom `style` **replaces** that default, so forgetting `flex: 1` collapses the root and the app renders nothing. - **Nested roots are ignored** except the top-most one, so adding one when a dependency might already render one is harmless. - **On Android, `Modal` content** renders outside the main hierarchy and needs its own root view wrapped around it. - With a native navigation library that hosts each screen separately, **every screen** needs a root view. In development builds, a `GestureDetector` rendered outside any root view throws an error saying it must be a descendant of `GestureHandlerRootView`. ## Building a gesture with a hook Gesture Handler 3 introduced a hook API. You pass everything, including callbacks and settings, in one configuration object: | Gesture Handler 2 builder | Gesture Handler 3 hook | |---|---| | `Gesture.Pan()` | `usePanGesture()` | | `Gesture.Tap()` | `useTapGesture()` | | `Gesture.Pinch()` | `usePinchGesture()` | | `Gesture.LongPress()` | `useLongPressGesture()` | | `Gesture.Simultaneous(a, b)` | `useSimultaneousGestures(a, b)` | The builder still works but is documented as legacy, and relations cannot be set between gestures made with the two APIs. ## GestureDetector `GestureDetector` takes a `gesture` prop, which is either a single gesture or a composed one, and attaches it to the view it wraps. 1. Create the gesture in the component with a hook. 2. Wrap the target view: `<GestureDetector gesture={pan}><View /></GestureDetector>`. 3. Keep **one gesture instance per detector**; sharing an instance between detectors is undefined behaviour. 4. Do **not nest detectors that mix the hook API and the builder API**. Since Gesture Handler 3, `GestureDetector` is a host component of its own, which can disturb components that depend on their view hierarchy, such as SVG shapes or nested `Text`. For those, the library adds `InterceptingGestureDetector` with `VirtualGestureDetector` inside it. ## In a photo viewer For a pinch-to-zoom photo viewer inside a horizontal pager, the root view wraps the app, each photo page builds its pinch and pan gestures with hooks, and a `GestureDetector` wraps the photo. The pager and the photos then sit under the same root, which is what later lets you declare that a pinch should hold the pager still. ## Common mistakes - A custom root `style` without `flex: 1`, producing a blank screen. - Gestures inside an Android `Modal` that never fire because the modal content has no root view. - Passing one gesture object to two detectors, for example in a list, instead of creating it inside each row. - Expecting relations to work across two separate roots. ## Installing and placing it in a real app - **Install it as a direct dependency** of the app (`npx expo install react-native-gesture-handler` in an Expo project). If it only arrives transitively, as another library's peer dependency, codegen does not run for it and the app fails with `TurboModuleRegistry.getEnforcing(...): 'RNGestureHandlerModule' could not be found`. - **Rebuild native code** after installing: in an Expo development build that means running prebuild again; in a bare app, reinstalling pods for iOS. - **Wrap the navigator**, not each screen, as the default. The documentation says wrapping the navigator is generally sufficient, and to wrap individual screens only if gestures misbehave there. - **Library authors** can wrap their own component in a root view, because nested roots are ignored and it spares users an extra setup step. - **Keep gesture creation inside the component** that renders the detector, so each photo page gets its own gesture objects.
- Why do Gesture Handler gestures inside a React Native Modal stop working on Android, and what fixes it?A `Modal`'s content is rendered in a separate native window rather than under the app's `GestureHandlerRootView`, so on Android the library is not intercepting touches there. Wrap the modal's content in its own `GestureHandlerRootView`. The same applies to native navigation libraries that host every screen separately.
- In Gesture Handler 3, why can a GestureDetector break gestures on SVG shapes or nested Text, and what replaces it there?Since version 3 the detector is a host component of its own, so it inserts a native view into the hierarchy, and components such as SVG shapes or nested `Text` rely on their exact hierarchy. Wrap the container in `InterceptingGestureDetector` and put `VirtualGestureDetector` around the inner element; the virtual detector attaches gestures without adding a host view.
saying these in an interview costs you the question
- Every screen always needs its own GestureHandlerRootView
- Passing style={{backgroundColor: 'black'}} to the root view keeps its flex: 1 default
- One pan gesture object can be shared by all rows of a list
- Gesture relations work across two separate GestureHandlerRootViews
- A GestureDetector outside the root view just falls back to the responder system