skip to content

In React Native Gesture Handler 3, what do GestureHandlerRootView and GestureDetector each do, and how do you attach a pan gesture to a view?

level: juniorimportance: should knowfreq 45%

answer

  1. one root near the app entry
  2. a custom style replaces flex: 1
  3. hooks build the gesture config
  4. the detector wraps the target view
  5. one gesture instance per detector

basics

~20 s

GestureHandlerRootView, 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 lines
tsx
import {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

for a junior

Recall the two pieces: a root view near the app entry and a GestureDetector around the view, fed by a hook such as usePanGesture.

for a middle

Explain the root view's rules: default flex style, one root for related gestures, and the Android Modal case, plus one gesture per detector.

for a senior

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.

for a principal

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