---
title: Mastering React Native Gesture Handler 3: The Complete Guide to the New Hooks API, Relations, and Worklet Workflows
publishedAt: 2026-08-29
summary: A comprehensive guide to React Native Gesture Handler 3, exploring the shift to hook-based gesture definitions, renamed lifecycle callbacks, declarative relations, and direct Reanimated SharedValue binding.
---

# Mastering React Native Gesture Handler 3: The Complete Guide to the New Hooks API, Relations, and Worklet Workflows

React Native Gesture Handler 3 introduces a major modernization of mobile gesture handling by transitioning from the legacy builder pattern (`Gesture.Pan()`) to a fully hook-based API (`usePanGesture()`, `useTapGesture()`, `usePinchGesture()`). Designed specifically for React Native's New Architecture (Fabric), Gesture Handler 3 streamlines callback lifecycles, integrates seamlessly with React Compiler patterns, and introduces direct `SharedValue` configuration binding.

### Executive Summary (TL;DR)
- **Hook-Based Declarations**: Builder chains (`Gesture.Pan().onUpdate(...)`) are replaced with dedicated hooks (`usePanGesture({ onUpdate: ... })`), improving alignment with standard React idioms.
- **Modernized Callbacks**: `onStart` is renamed to `onActivate`, `onEnd` is renamed to `onDeactivate`, and the old `(event, success)` parameter signature is replaced with a single `event.canceled` property.
- **Streamlined Delta Tracking**: The separate `onChange` callback has been merged directly into `onUpdate`, which now exposes properties like `changeX` and `changeY`.
- **Declarative Relations**: Gesture arbitration is now configured via properties like `simultaneousWith`, `requireToFail`, and `block` directly in the hook config, or composed using helper hooks like `useSimultaneousGestures`.
- **Dynamic SharedValues**: Gesture configuration options (e.g., `enabled`, activation thresholds) can consume Reanimated `SharedValue` instances directly without triggering component re-renders.

---

## The Paradigm Shift: From Builder Chains to Idiomatic Hooks

Gesture Handler 3 replaces the chainable `Gesture.*` builder pattern with dedicated React hooks such as `usePanGesture`, `useTapGesture`, and `usePinchGesture`. Instead of mutating and chaining configuration methods on an object instance, developers now pass a structured configuration object directly into the hook. This architectural pivot simplifies component logic, improves TypeScript inference, and natively supports modern React paradigms and compiler workflows.

In Gesture Handler 2, gestures were declared using chainable builder objects:

```tsx
// Legacy RNGH v2 Builder Pattern (Deprecated)
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

const panGesture = Gesture.Pan()
  .minDistance(10)
  .onStart(() => {
    'worklet';
    console.log('Started');
  })
  .onUpdate((event) => {
    'worklet';
    translateX.value = event.translationX;
  })
  .onEnd((event, success) => {
    'worklet';
    if (success) {
      console.log('Completed');
    }
  });
```

In [React Native Gesture Handler Documentation](https://docs.swmansion.com/react-native-gesture-handler/) v3, every gesture type is instantiated via a top-level hook. The configuration object accepts options and lifecycle callbacks cleanly:

```tsx
// Modern RNGH v3 Hook Pattern
import { usePanGesture, GestureDetector } from 'react-native-gesture-handler';

const panGesture = usePanGesture({
  minDistance: 10,
  onActivate: () => {
    'worklet';
    console.log('Activated');
  },
  onUpdate: (event) => {
    'worklet';
    translateX.value = event.translationX;
  },
  onDeactivate: (event) => {
    'worklet';
    if (!event.canceled) {
      console.log('Completed successfully');
    }
  },
});
```

### Available Gesture Hooks in RNGH 3
The new API provides dedicated hooks for every standard gesture type:
- `usePanGesture(config)`: Continuous panning, dragging, and velocity tracking.
- `useTapGesture(config)`: Discrete single or multi-tap recognition.
- `usePinchGesture(config)`: Multi-touch pinch-to-zoom scaling.
- `useRotationGesture(config)`: Two-finger rotation tracking.
- `useLongPressGesture(config)`: Timed touch-and-hold interactions.
- `useFlingGesture(config)`: Directional swipe gestures with distance thresholds.
- `useNativeGesture(config)`: Interoperability with native platform scroll containers.

---

## Callback Modernization: Renamed Lifecycles and Event Properties

Gesture Handler 3 standardizes callback naming to more accurately reflect native gesture state transitions. Callbacks like `onStart` and `onEnd` have been updated to `onActivate` and `onDeactivate`, while `onChange` is unified into `onUpdate`. In addition, gesture outcome checking now uses an explicit `canceled` boolean property on the event object rather than a separate parameter.

```
       [ Touch Down ]
             │
             ▼
     ┌───────────────┐
     │    onBegin    │
     └───────┬───────┘
             │
    (Threshold Passed)
             │
             ▼
     ┌───────────────┐
     │  onActivate   │  (Formerly onStart)
     └───────┬───────┘
             │
    (Continuous Moves)
             │
             ▼
     ┌───────────────┐
     │   onUpdate    │  (Now includes delta changeX / changeY)
     └───────┬───────┘
             │
    (Touch Lifted / Cancelled)
             │
             ▼
     ┌───────────────┐
     │ onDeactivate  │  (Formerly onEnd — checks event.canceled)
     └───────┬───────┘
             │
             ▼
     ┌───────────────┐
     │  onFinalize   │  (Always runs for terminal cleanup)
     └───────────────┘
```

### Key Lifecycle Changes

| Legacy Callback (v2) | Modern Callback (v3) | Behavioral Details |
| :--- | :--- | :--- |
| `onStart` | `onActivate` | Fires precisely when the gesture transitions from `BEGAN` to `ACTIVE`. |
| `onEnd` | `onDeactivate` | Fires when the gesture transitions out of `ACTIVE`. Receives `event` containing `event.canceled`. |
| `onChange` | *Merged into `onUpdate`* | `onUpdate` now directly provides delta fields (`changeX`, `changeY`, `scaleChange`, etc.). |
| `onTouchesCancelled` | `onTouchesCancel` | Standardized naming for direct multi-touch cancellation events. |
| `onFinalize` | `onFinalize` | Unchanged; guaranteed to fire on every terminal transition (`END`, `FAILED`, `CANCELLED`). |

### Handling Success vs. Cancellation
In v2, `onEnd` received `(event, success)`. In v3, `onDeactivate` receives the gesture event payload containing `event.canceled`:

```tsx
const pan = usePanGesture({
  onDeactivate: (event) => {
    'worklet';
    if (event.canceled) {
      // The gesture was interrupted by the OS or a competing gesture
      console.log('Gesture was cancelled');
      return;
    }
    // Clean completion
    console.log('Gesture completed with final velocity:', event.velocityX);
  },
});
```

---

## Dynamic Configuration and Direct SharedValue Binding

Gesture Handler 3 introduces first-class integration with Reanimated SharedValues directly inside hook configuration parameters. Developers can bind dynamic values—such as toggling `enabled` or updating activation thresholds—without causing React re-render cycles. This ensures gesture configurations stay responsive while keeping the component tree completely static.

```tsx
import React from 'react';
import { usePanGesture } from 'react-native-gesture-handler';
import { useSharedValue } from 'react-native-reanimated';

export const DynamicDraggable = () => {
  const isDragAllowed = useSharedValue(true);
  const minPanDistance = useSharedValue(20);

  // SharedValues can be passed directly into configuration props
  const panGesture = usePanGesture({
    enabled: isDragAllowed,
    minDistance: minPanDistance,
    onUpdate: (e) => {
      'worklet';
      // Handle drag updates
    },
  });

  return (
    // Component markup
    null
  );
};
```

### Disabling Reanimated on Specific Gestures
If you are building a lightweight gesture that does not require Reanimated worklet bridging (for example, simple analytics tracking on the JavaScript thread), you can pass `disableReanimated: true` in the configuration:

```tsx
const jsTapGesture = useTapGesture({
  disableReanimated: true,
  onActivate: () => {
    // Runs directly on the JS thread without worklet wrapping
    trackAnalyticsEvent('button_tapped');
  },
});
```

---

## Gesture Relations: Declarative Coordination and Conflict Resolution

Coordinating competing or simultaneous gestures in RNGH 3 is accomplished by passing referenced gesture instances into relationship properties within the hook configuration. Options such as `simultaneousWith`, `requireToFail`, and `block` allow gestures to declare dependencies explicitly. For grouping simultaneous gestures inside a single detector, the `useSimultaneousGestures` hook provides a clean declarative wrapper.

```
                  usePanGesture & usePinchGesture
                                 │
                 ┌───────────────┴───────────────┐
                 ▼                               ▼
       [ Pan Gesture Hook ]             [ Pinch Gesture Hook ]
   (simultaneousWith: [pinch])       (simultaneousWith: [pan])
                 │                               │
                 └───────────────┬───────────────┘
                                 │
                                 ▼
                 useSimultaneousGestures(pan, pinch)
                                 │
                                 ▼
                     <GestureDetector gesture={...}>
```

### The Three Core Relation Properties

1. **`simultaneousWith`**: Allows multiple gestures to activate concurrently without cancelling one another.
   ```tsx
   const pan = usePanGesture({
     simultaneousWith: [pinchGesture],
   });
   ```

2. **`requireToFail`**: Delays gesture activation until the specified gesture explicitly fails. Commonly used to distinguish between single-tap and double-tap interactions.
   ```tsx
   const singleTap = useTapGesture({
     requireToFail: [doubleTapGesture],
     onActivate: () => {
       'worklet';
       console.log('Single tap confirmed');
     },
   });
   ```

3. **`block`**: Explicitly blocks target gestures from activating while the declaring gesture is recognized.
   ```tsx
   const drawerPan = usePanGesture({
     block: [nativeScrollGesture],
   });
   ```

### Composing Gestures with `useSimultaneousGestures`
When attaching multiple cooperating gestures to the same view, combine them using `useSimultaneousGestures`:

```tsx
import {
  usePanGesture,
  usePinchGesture,
  useSimultaneousGestures,
  GestureDetector,
} from 'react-native-gesture-handler';

const pan = usePanGesture({ /* ... */ });
const pinch = usePinchGesture({ /* ... */ });

// Combine both gestures into a single detector handle
const composedGesture = useSimultaneousGestures(pan, pinch);

return (
  <GestureDetector gesture={composedGesture}>
    <Animated.View style={animatedStyle} />
  </GestureDetector>
);
```

---

## Complete Implementation: Interactive Multi-Touch Canvas

Combining multiple gesture hooks enables sophisticated multi-touch canvas manipulation with smooth spring physics and momentum. In this production example, `usePanGesture`, `usePinchGesture`, and `useTapGesture` are coordinated using gesture relations and Reanimated shared values. The canvas supports concurrent panning and zooming while cleanly handling double-tap resets and cancellation lifecycles.

```tsx
import React from 'react';
import { StyleSheet, View } from 'react-native';
import {
  GestureDetector,
  GestureHandlerRootView,
  usePanGesture,
  usePinchGesture,
  useTapGesture,
  useSimultaneousGestures,
} from 'react-native-gesture-handler';
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withDecay,
  withSpring,
} from 'react-native-reanimated';

const SPRING_CONFIG = {
  damping: 15,
  stiffness: 120,
  mass: 0.8,
};

export const InteractiveCanvas: React.FC = () => {
  const translationX = useSharedValue(0);
  const translationY = useSharedValue(0);
  const prevTranslationX = useSharedValue(0);
  const prevTranslationY = useSharedValue(0);

  const scale = useSharedValue(1);
  const prevScale = useSharedValue(1);

  const isInteracting = useSharedValue(false);

  // 1. Pan Gesture: Dragging and momentum decay
  const panGesture = usePanGesture({
    onBegin: () => {
      'worklet';
      isInteracting.value = true;
      prevTranslationX.value = translationX.value;
      prevTranslationY.value = translationY.value;
    },
    onUpdate: (event) => {
      'worklet';
      translationX.value = prevTranslationX.value + event.translationX;
      translationY.value = prevTranslationY.value + event.translationY;
    },
    onDeactivate: (event) => {
      'worklet';
      if (!event.canceled) {
        translationX.value = withDecay({
          velocity: event.velocityX,
          clamp: [-400, 400],
        });
        translationY.value = withDecay({
          velocity: event.velocityY,
          clamp: [-600, 600],
        });
      }
    },
    onFinalize: () => {
      'worklet';
      isInteracting.value = false;
    },
  });

  // 2. Pinch Gesture: Focal scaling
  const pinchGesture = usePinchGesture({
    simultaneousWith: [panGesture],
    onBegin: () => {
      'worklet';
      isInteracting.value = true;
      prevScale.value = scale.value;
    },
    onUpdate: (event) => {
      'worklet';
      const nextScale = prevScale.value * event.scale;
      scale.value = Math.min(Math.max(nextScale, 0.5), 4.0);
    },
    onDeactivate: (event) => {
      'worklet';
      if (!event.canceled && scale.value < 1) {
        scale.value = withSpring(1, SPRING_CONFIG);
      }
    },
    onFinalize: () => {
      'worklet';
      isInteracting.value = false;
    },
  });

  // 3. Tap Gesture: Double-tap to reset transformation
  const doubleTapGesture = useTapGesture({
    numberOfTaps: 2,
    maxDelayMs: 250,
    onActivate: () => {
      'worklet';
      translationX.value = withSpring(0, SPRING_CONFIG);
      translationY.value = withSpring(0, SPRING_CONFIG);
      scale.value = withSpring(1, SPRING_CONFIG);
    },
  });

  // 4. Compose Pan and Pinch
  const panAndPinch = useSimultaneousGestures(panGesture, pinchGesture);

  // 5. Animated Style
  const animatedStyle = useAnimatedStyle(() => {
    'worklet';
    return {
      transform: [
        { translateX: translationX.value },
        { translateY: translationY.value },
        { scale: scale.value },
      ],
      opacity: isInteracting.value ? 0.9 : 1.0,
    };
  });

  return (
    <GestureHandlerRootView style={styles.container}>
      <View style={styles.canvasBounds}>
        <GestureDetector gesture={doubleTapGesture}>
          <GestureDetector gesture={panAndPinch}>
            <Animated.View style={[styles.targetCard, animatedStyle]} />
          </GestureDetector>
        </GestureDetector>
      </View>
    </GestureHandlerRootView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#090d16',
  },
  canvasBounds: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    overflow: 'hidden',
  },
  targetCard: {
    width: 240,
    height: 240,
    borderRadius: 24,
    backgroundColor: '#0284c7',
    elevation: 8,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 8 },
    shadowOpacity: 0.35,
    shadowRadius: 16,
  },
});
```

---

## Migration Guide: Upgrading from Gesture Handler 2 to 3

Upgrading an existing application to Gesture Handler 3 requires replacing builder chains with their respective hook counterparts and updating callback identifiers. Teams must ensure their application runs on React Native 0.82 or newer with the New Architecture enabled, as legacy bridge support is removed. Following a structured migration checklist ensures seamless adoption without breaking gesture arbitration.

### Migration Checklist

1. **Verify Minimum Requirements**: Ensure your project is on React Native 0.82+ with the New Architecture (Fabric) enabled. Check the [React Native Gesture Handler GitHub Repository](https://github.com/software-mansion/react-native-gesture-handler) for release notes.
2. **Convert Builders to Hooks**: Replace calls to `Gesture.Pan()`, `Gesture.Tap()`, and `Gesture.Pinch()` with `usePanGesture({})`, `useTapGesture({})`, and `usePinchGesture({})`.
3. **Rename Lifecycle Callbacks**:
   - Replace `.onStart(...)` with `onActivate: (...) => {}`.
   - Replace `.onEnd(...)` with `onDeactivate: (event) => {}`.
   - Replace `.onTouchesCancelled(...)` with `onTouchesCancel: (...) => {}`.
4. **Update Cancellation Checks**: Change `onEnd((event, success) => { if (success) ... })` to `onDeactivate((event) => { if (!event.canceled) ... })`.
5. **Migrate `onChange`**: Move delta calculation logic from `.onChange()` into `onUpdate`, reading `event.changeX`, `event.changeY`, or `event.scaleChange`.
6. **Update Composition**: Replace `Gesture.Simultaneous(g1, g2)` with `useSimultaneousGestures(g1, g2)` or configure `simultaneousWith: [g2]` inside the hook config.
7. **Adopt the New `<Touchable>`**: Migrate legacy `RectButton`, `BorderlessButton`, or `TouchableOpacity` wrappers to RNGH 3's unified `<Touchable>` component. Refer to the [Software Mansion Reanimated Documentation](https://docs.swmansion.com/react-native-reanimated/) for compatible animation patterns.

---

## Frequently Asked Questions

### Can I mix the legacy Gesture builder API with the new hook-based API in the same component?
No, mixing the legacy `Gesture.*` builder API with the new `use*Gesture` hook API within the same interaction hierarchy is not supported. Gesture relationships such as `simultaneousWith`, `requireToFail`, and `block` require all participating gestures to use the same internal representation. When migrating a screen or component, convert all associated gestures to the new hook API.

### How does event cancellation work in RNGH 3 compared to RNGH 2?
In Gesture Handler 2, `onEnd` received a second argument boolean named `success` (e.g., `(event, success) => {}`). In Gesture Handler 3, `onDeactivate` receives a single `event` object containing a `canceled` boolean property. A value of `event.canceled === true` indicates that the gesture was interrupted, cancelled by the operating system, or superseded by a competing recognizer.

### Why did Gesture Handler 3 remove the onChange callback in favor of onUpdate?
In earlier versions, `onChange` was provided as a convenience wrapper around `onUpdate` to calculate change deltas (the difference in touch position or scale between the current and previous frame). In Gesture Handler 3, these delta calculations (`changeX`, `changeY`, `scaleChange`, etc.) are computed natively and attached directly to the `onUpdate` event object, removing the need for a redundant callback.

---

### About the Author
**Furkan Çetinkaya** is a Mobile-focused Software Developer specializing in React Native, native bridge integrations (Kotlin & Swift), and supporting backend services. Experienced in maintaining high-impact mobile applications and developer SDKs.
- [GitHub](https://github.com/cetfu)
- [LinkedIn](https://www.linkedin.com/in/cetfu)