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.
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.
onStartbecomesonActivate.onEndbecomesonDeactivate.onTouchesCancelledbecomesonTouchesCancel.- The old
successboolean becomesevent.canceled, with inverted meaning. onChangeis folded intoonUpdate; readchangeX,changeY, and related change values there.stateandoldStateare 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
RectButtonstyle interactions withTouchableplus underlay or Android ripple props. - Replace
BorderlessButtonwithTouchableand 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
createNativeWrapperis deprecated and exported aslegacy_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
GestureHandlerRootViewas the related gestures. - Android modals: wrap modal content in
GestureHandlerRootViewwhen 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 prebuildand 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.canceledand 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.82or 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-workletsfor UI-thread gestures. - Wrap the root app with
GestureHandlerRootView. - Move builder gestures to hook gestures like
usePanGesture. - Replace renamed callbacks and audit
event.canceledlogic. - 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.