Back to Tutorials
Expo Migration

React Native Gesture Handler 3.0: Expo Migration Guide (2026)

Upgrade an Expo app from Gesture Handler 2 to 3 with the new hook API, Reanimated setup, renamed callbacks, relation hooks, and a small pan-card example you can actually use.

Published June 4 ยท Updated July 29, 2026 14 min read Paddy B
Expo React Native Gesture Handler 3.0 Reanimated Migration

The short version

Gesture Handler 3 moves the primary API from builder chains like Gesture.Pan().onUpdate(...) to hooks like usePanGesture({ onUpdate }). For Expo apps, the upgrade is mostly dependency alignment, root setup, callback renames, and a careful pass over composed gestures.

If your Expo app already uses Gesture Handler 2 with Reanimated, the mental model stays familiar: gestures still drive shared values, and animated styles still render the movement. The difference is that gesture configuration now lives in hook options, which fits React components better and avoids a lot of manual memoization.

The official compatibility line is important: Gesture Handler 3 starts at React Native 0.82. Check your Expo SDK and React Native version before you touch app code, especially if you are upgrading an older production app.

Expo SDK 57 version note

Expo SDK 57 uses React Native 0.86, so it meets Gesture Handler 3's React Native requirement. However, Expo's SDK 57 package reference recommends Gesture Handler ~2.32.0. Installing Gesture Handler 3 is an intentional opt-in and requires a development build; the SDK 57 Expo Go client contains the 2.x native library.

1. Check the Expo app first

Start by confirming the app is on Expo SDK 57 / React Native 0.86. Then opt into Gesture Handler 3 explicitly instead of asking Expo to install its default 2.32 package.

npx expo install react-native-gesture-handler@^3.0.0 npx expo install react-native-reanimated react-native-worklets

Reanimated plus react-native-worklets is the recommended path for gesture-driven interactions that need to run smoothly on the UI thread. You can still use React Native's Animated API for simpler cases, but most real gesture UI belongs with Reanimated.

Expo Go cannot load a different native Gesture Handler version from JavaScript. Create a development build after changing the dependency:

npx expo prebuild npx expo run:ios npx expo run:android

Version gate

Do not force Gesture Handler 3 into an app below React Native 0.82, and do not expect SDK 57's Expo Go client to provide it. Upgrade the Expo SDK first, then use a development build.

2. Keep GestureHandlerRootView at the app root

Your gestures need to live under GestureHandlerRootView. Put it as close to the app root as possible so screen-level and component-level gestures can relate to each other correctly.

import { GestureHandlerRootView } from "react-native-gesture-handler"; import { Stack } from "expo-router"; export default function RootLayout() { return ( <GestureHandlerRootView style={{ flex: 1 }}> <Stack /> </GestureHandlerRootView> ); }

In a classic Expo app without Expo Router, the same idea applies around your actual app component.

import { GestureHandlerRootView } from "react-native-gesture-handler"; export default function App() { return ( <GestureHandlerRootView style={{ flex: 1 }}> <ActualApp /> </GestureHandlerRootView> ); }

On Android, content inside a native Modal may need its own GestureHandlerRootView. That is one of those boring setup details that saves you from mysterious "gesture works everywhere except this sheet" debugging later.

3. Move from builders to hooks

The big migration is mechanical, but it changes how the code reads. In Gesture Handler 2 you probably had builder chains.

import { Gesture } from "react-native-gesture-handler"; const pan = Gesture.Pan() .onStart(() => { startX.value = translateX.value; }) .onUpdate((event) => { translateX.value = startX.value + event.translationX; }) .onEnd((event, success) => { if (success) { translateX.value = withSpring(0); } });

In Gesture Handler 3, the same pan becomes hook configuration. Callback names also shift: onStart becomes onActivate, onEnd becomes onDeactivate, and the old success boolean becomes event.canceled.

import { usePanGesture } from "react-native-gesture-handler"; const pan = usePanGesture({ onActivate: () => { startX.value = translateX.value; }, onUpdate: (event) => { translateX.value = startX.value + event.translationX; }, onDeactivate: (event) => { if (!event.canceled) { translateX.value = withSpring(0); } }, });

The shape is easier to scan once you get used to it. Gesture options, thresholds, and callbacks all sit in one object instead of being spread across chained method calls.

4. Build a small pan-card component

Here is a complete draggable card that uses Gesture Handler 3 hooks with Reanimated shared values. It is intentionally small so the migration pattern is obvious.

import { StyleSheet, Text } from "react-native"; import { GestureDetector, usePanGesture } from "react-native-gesture-handler"; import Animated, { useAnimatedStyle, useSharedValue, withSpring, } from "react-native-reanimated"; export function PanCard() { const translateX = useSharedValue(0); const translateY = useSharedValue(0); const startX = useSharedValue(0); const startY = useSharedValue(0); const pan = usePanGesture({ onActivate: () => { startX.value = translateX.value; startY.value = translateY.value; }, onUpdate: (event) => { translateX.value = startX.value + event.translationX; translateY.value = startY.value + event.translationY; }, onDeactivate: (event) => { if (!event.canceled) { translateX.value = withSpring(0); translateY.value = withSpring(0); } }, }); const cardStyle = useAnimatedStyle(() => ({ transform: [ { translateX: translateX.value }, { translateY: translateY.value }, ], })); return ( <GestureDetector gesture={pan}> <Animated.View style={[styles.card, cardStyle]}> <Text style={styles.title}>Drag me</Text> <Text style={styles.body}>Gesture Handler 3 hook API</Text> </Animated.View> </GestureDetector> ); } const styles = StyleSheet.create({ card: { width: 260, minHeight: 150, justifyContent: "center", borderRadius: 18, padding: 20, backgroundColor: "#111827", shadowColor: "#000", shadowOpacity: 0.24, shadowRadius: 18, elevation: 8, }, title: { color: "#f8fafc", fontSize: 24, fontWeight: "800", }, body: { color: "#94a3b8", marginTop: 8, }, });

This is deliberately not a sortable list. For reorder screens, read the custom drag-and-drop guide separately. This article is about the Gesture Handler 3 migration surface: hooks, callbacks, relations, and setup.

5. Update callback and event assumptions

Most bugs in a Gesture Handler 3 migration come from old callback names or old event assumptions hiding in working code. Search the app for the old names and replace them intentionally.

  • onStart becomes onActivate.
  • onEnd becomes onDeactivate.
  • onTouchesCancelled becomes onTouchesCancel.
  • The old success boolean becomes event.canceled, with inverted meaning.
  • onChange is folded into onUpdate; read changeX, changeY, and related change values there.
  • state and oldState are no longer on event objects, so use the lifecycle callbacks instead.
const tap = useTapGesture({ onDeactivate: (event) => { if (event.canceled) { return; } runOnJS(openDetails)(); }, });

That event.canceled rename is small, but it is the one I would double-check in code review. It is easy to preserve the old branch and accidentally reverse the behavior.

6. Migrate gesture relations

Composed gestures also move to hooks. A Gesture Handler 2 simultaneous gesture looked like this:

const pan = Gesture.Pan(); const pinch = Gesture.Pinch(); const gesture = Gesture.Simultaneous(pan, pinch);

In Gesture Handler 3, use the relation hook that describes the relationship.

import { usePanGesture, usePinchGesture, useSimultaneousGestures, } from "react-native-gesture-handler"; const pan = usePanGesture({}); const pinch = usePinchGesture({}); const gesture = useSimultaneousGestures(pan, pinch);

The key mappings are Gesture.Race() to useCompetingGestures(), Gesture.Simultaneous() to useSimultaneousGestures(), and Gesture.Exclusive() to useExclusiveGestures().

For cross-component relationships, config names also changed. Use simultaneousWith, requireToFail, and block instead of the old external-gesture builder methods.

7. Replace old touchable and button assumptions

Gesture Handler 3 introduces Touchable as the recommended replacement for older button and touchable APIs. Existing buttons may still work through deprecated or legacy exports, but new code should move toward Touchable.

  • Replace RectButton style interactions with Touchable plus underlay or Android ripple props.
  • Replace BorderlessButton with Touchable and a borderless Android ripple when you need that native feel.
  • Use legacy-prefixed components only when you need to preserve behavior while migrating a larger app in stages.
  • Remember that createNativeWrapper is deprecated and exported as legacy_createNativeWrapper.

I would avoid converting every button and every gesture in the same commit. Upgrade the package, migrate the gesture hot paths, then clean up old touchables once screens are stable.

Expo migration gotchas

  • Root view placement: if a gesture does not fire, confirm the component is under the same GestureHandlerRootView as the related gestures.
  • Android modals: wrap modal content in GestureHandlerRootView when gestures inside the modal are ignored.
  • Development builds: SDK 57's Expo Go client includes Gesture Handler 2.32. After opting into 3.x, run npx expo prebuild and rebuild the app.
  • Reanimated setup: keep the Reanimated Babel/plugin setup aligned with the version your Expo SDK expects.
  • Old success checks: replace them with !event.canceled and verify the branch still means "gesture completed".
  • Existing drag lists: do not rewrite complex reorder UI while also upgrading the library. Migrate the API first, then improve the interaction.

Final checklist

  • Confirm the app is on React Native 0.82 or newer through your Expo SDK.
  • On Expo SDK 57, opt into 3.x explicitly with npx expo install react-native-gesture-handler@^3.0.0.
  • Install and configure Reanimated plus react-native-worklets for UI-thread gestures.
  • Wrap the root app with GestureHandlerRootView.
  • Move builder gestures to hook gestures like usePanGesture.
  • Replace renamed callbacks and audit event.canceled logic.
  • Migrate composed gestures to relation hooks.
  • Test iOS, Android, web if supported, and any modal or bottom-sheet screens.

Related Expo guides

Need this built for you? I take on contract Expo and React Native work — see how I can help.

Support tutorials