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

Published: August 29, 2026

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:

// 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 v3, every gesture type is instantiated via a top-level hook. The configuration object accepts options and lifecycle callbacks cleanly:

// 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:

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.

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:

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.

    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.

    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.

    const drawerPan = usePanGesture({
      block: [nativeScrollGesture],
    });

Composing Gestures with useSimultaneousGestures

When attaching multiple cooperating gestures to the same view, combine them using useSimultaneousGestures:

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.

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 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 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.