From b672a3ded58107e634d39e729e5f6ac845faa34d Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Tue, 3 Feb 2026 17:09:25 +0100 Subject: [PATCH 1/9] Add llm skill --- .../gesture-handler-3-migration/SKILL.md | 548 ++++++++++++++++++ 1 file changed, 548 insertions(+) create mode 100644 .cursor/skills/gesture-handler-3-migration/SKILL.md diff --git a/.cursor/skills/gesture-handler-3-migration/SKILL.md b/.cursor/skills/gesture-handler-3-migration/SKILL.md new file mode 100644 index 0000000000..1b1f777db2 --- /dev/null +++ b/.cursor/skills/gesture-handler-3-migration/SKILL.md @@ -0,0 +1,548 @@ +--- +name: gesture-handler-3-migration +description: Migrates files containing React Native components which use the React Native Gesture Handler 2 API to Gesture Handler 3. +--- + +# Migrate to Gesture Handler 3 + +This skill goes over the components using the Gesture Handler builder-based API updates it to use the new hook-based API. It also handles updating types and components which may have changed in the new version. + +## When to Use + +- Updating the usage of components imported from `react-native-gesture-handler` +- Upgrading to Gesture Handler 3 +- Migrating to the new hook-based gesture API + +## Instructions + +Use the instructions below to correctly replace all legacy APIs with the modern ones. + +### Migrating gestures + +The most important change brought by the Gesture Handler 3 is the new hooks API. Migration is pretty straightforward. Instead of calling builder methods, everything is passed as a configuration object. +Unless specified, the names and types of callbacks and properties remain unchanged. + +Old API: +```js +import { Gesture } from 'react-native-gesture-handler'; + +const gesture = Gesture.Pan() + .onBegin(() => { + console.log('Pan!'); + }) + .minDistance(25); +``` + +New API: +```js +import { usePanGesture } from 'react-native-gesture-handler'; + +const gesture = usePanGesture({ + onBegin: () => { + console.log('Pan!'); + }, + minDistance: 25, +}); +```` + +`ForceTouch` gesture is not available in hooks API. If it's still being used, leave it as is. + +All gestures from the old API and their counterparts in the new one: + +| RNGH2 | RNGH3 | +| ---------------------- | ----------------------- | +| `Gesture.Pan()` | `usePanGesture()` | +| `Gesture.Tap()` | `useTapGesture()` | +| `Gesture.LongPress()` | `useLongPressGesture()` | +| `Gesture.Rotation()` | `useRotationGesture()` | +| `Gesture.Pinch()` | `usePinchGesture()` | +| `Gesture.Fling()` | `useFlingGesture()` | +| `Gesture.Hover()` | `useHoverGesture()` | +| `Gesture.Native()` | `useNativeGesture()` | +| `Gesture.Manual()` | `useManualGesture()` | +| `Gesture.ForceTouch()` | *Not available in hook API* | + +#### Callback changes + +In Gesture Handler 3 some of the callbacks were renamed, namely: + +| RNGH2 | RNGH3 | +| --------- | -------------- | +| `onStart` | `onActivate` | +| `onEnd` | `onDeactivate` | +| `onTouchesCancelled` | `onTouchesCancel` | + +Old API: +```js +import { Gesture } from 'react-native-gesture-handler'; + +const gesture = Gesture.Pan() + .onStart(() => { + console.log('Pan activated!'); + }) + .onEnd(() => { + console.log('Pan deactivated!'); + }) + .onTouchesCancelled(() => { + console.log('Touches canceled!'); + }); +``` + +New API: +```js +import { usePanGesture } from 'react-native-gesture-handler'; + +const gesture = usePanGesture({ + onActivate: () => { + console.log('Pan activated!'); + }, + onDeactivate: () => { + console.log('Pan deactivated!'); + }, + onTouchesCancel: () => { + console.log('Touches canceled!'); + }, +}); +``` + +In the hooks API `onChange` is no longer available. Instead the `*change*` properties were moved to the event available inside `onUpdate`. + +Old API: +```js +import { Gesture } from 'react-native-gesture-handler'; + +const gesture = Gesture.Pan() + .onChange((event) => { + console.log(event.changeX, event.changeY); + }) +``` + +New API: +```js +import { usePanGesture } from 'react-native-gesture-handler'; + +const gesture = usePanGesture({ + onUpdate: (event) => { + console.log(event.changeX, event.changeY); + } +}); +``` + +#### StateManager + +In Gesture Handler 3, `stateManager` is no longer passed to `TouchEvent` callbacks. Instead, you should use the global `GestureStateManager`. + +`GestureStateManager` provides methods for iperative state management: +- .begin(handlerTag: number) +- .activate(handlerTag: number) +- .deactivate(handlerTag: number) (.end() in the old API) +- .fail(handlerTag: number) + +`handlerTag` can be obtained in two ways: +1. From the gesture object returned by the hook (`gesture.handlerTag`) +2. From the event inside callback (`event.handlerTag`) + +Callback definitions CANNOT reference the gesture that's being defined. In this scenario use events to get access to the handler tag. + +Old API: +```js +import { Gesture } from 'react-native-gesture-handler'; + +const manual = Gesture.Manual().onTouchesDown((e, stateManager) => { + stateManager.activate(); +}); +``` + +New API: +```js +import { useManualGesture, GestureStateManager } from 'react-native-gesture-handler'; + +const manual = useManualGesture({ + onTouchesDown: (e) => { + GestureStateManager.activate(e.handlerTag); + }, +}); +``` + +### Migrating relations + +#### Composed gestures + +Previously, composed gestures were created using `Gesture` object. In Gesture Handler 3, relations are set up using relation hooks. + +Old API: +```js +import { Gesture } from 'react-native-gesture-handler'; + +const pan1 = Gesture.Pan(); +const pan2 = Gesture.Pan(); + +const gesture = Gesture.Simultaneous(pan1, pan2); +``` + +New API: +```js +import { usePanGesture, useSimultaneousGestures } from 'react-native-gesture-handler'; + +const pan1 = usePanGesture({}); +const pan2 = usePanGesture({}); + +const gesture = useSimultaneousGestures(pan1, pan2); +``` + +All relations from the old API and their counterparts in the new one: + +| RNGH2 | RNGH3 | +| ----------------------------------------- | --------------------------- | +| `Gesture.Race()` | `useCompetingGestures()` | +| `Gesture.Simultaneous()` | `useSimultaneousGestures()` | +| `Gesture.Exclusive()` | `useExclusiveGestures()` | + + +#### Cross components relations properties + +Properties used to define cross-components interactions were renamed. + +Old API: +```js +import { Gesture } from 'react-native-gesture-handler'; + +const pan1 = Gesture.Pan(); +const pan2 = + Gesture.Pan().requireExternalGestureToFail(pan1); +``` + +New API: +```js +import { usePanGesture } from 'react-native-gesture-handler'; + +const pan1 = usePanGesture({}); +const pan2 = usePanGesture({ + requireToFail: pan1, +}); +``` + +Full changes are as follows: + +| RNGH2 | RNGH3 | +| ----------------------------------------- | --------------------------- | +| `.simultaneousWithExternalGesture` | `simultaneousWith:` | +| `.requireExternalGestureToFail` | `requireToFail:` | +| `.blocksExternalGesture` | `block:` | + +### GestureDetector + +The `GestureDetector` is a key component of `react-native-gesture-handler`. It supports gestures created either using the hooks API or the builder pattern (but those cannot be mixed, it's either or). Additionally, it allows for the recognition of multiple gestures through gesture composition. `GestureDetector` interacts closely with `Reanimated`. + +#### Reusing Gestures + +Using the same instance of a gesture across multiple Gesture Detectors may result in undefined behavior. + +### Integration with Reanimated + +Worklets' Babel plugin is setup in a way that automatically marks callbacks passed to gestures in the configuration chain as worklets. This means that you don't need to add a `'worklet';` directive at the beginning of the functions. Here is an example that will be automatically workletized: + +```jsx +const gesture = useTapGesture({ + onBegin: () => { + console.log(_WORKLET); + }, +}); +``` + +This one will not be workletized because the callback is defined outside of the gesture object: + +```jsx +const callback = () => { + console.log(_WORKLET); +}; + +const gesture = useTapGesture({ + onBegin: callback, +}); +``` + +It also won't work when wrapped with hooks like `useCallback` or `useMemo`, e.g: + +```jsx +const callback = useCallback(() => { + console.log(_WORKLET); +}, []); + +const gesture = useTapGesture({ + onBegin: callback, +}); +``` + +This one will not be workletized as well, due to the Worklet's plugin limitation - the callback cannot be wrapped by any other higher order function to be workletized: + +```jsx +const gesture = useTapGesture({ + onBegin: useCallback(() => { + console.log(_WORKLET); + }, []), +}); +``` + +In the above cases, you should add a `"worklet";` directive at the beginning of the callbacks, like so: + +```jsx +const callback = () => { + // highlight-next-line + 'worklet'; + console.log(_WORKLET); +}; + +const gesture = useTapGesture({ + onBegin: callback, +}); +``` + +```jsx +const callback = useCallback(() => { + // highlight-next-line + 'worklet;'; + console.log(_WORKLET); +}, []); + +const gesture = useTapGesture({ + onBegin: callback, +}); +``` + +```jsx +const gesture = useTapGesture({ + onBegin: useCallback(() => { + 'worklet'; + console.log(_WORKLET); + }, []), +}); +``` + +### Using SharedValue in gesture config + +RNGH3 allows to pass `SharedValue` to gestures' configurations. This allows to react to configuration changes without unnecessary rerenders. For example: + +```js +import * as React from 'react'; +import { Animated } from 'react-native'; +import { + GestureHandlerRootView, + GestureDetector, + useTapGesture, +} from 'react-native-gesture-handler'; +import { useSharedValue } from 'react-native-reanimated'; + +export default function App() { + // highlight-next-line + const taps = useSharedValue(2); + + const gesture = useTapGesture({ + // highlight-next-line + numberOfTaps: taps, + onDeactivate: () => { + taps.value += 1; + }, + }); + + return ( + + + + + + ); +} +``` + +### Disabling Reanimated + +Gestures created with the hook API have `Reanimated` integration enabled by default (if it's installed), meaning all callbacks are executed on the UI thread. There are two methods available to disable this behavior for a specific gesture. + +#### disableReanimated + +When `disableReanimated` is set to `true` in the gesture configuration, `Reanimated` integration will be completely turned off for that gesture throughout its entire lifecycle. This setting eliminates all interaction points with `Reanimated`, thereby reducing any potential overhead. Default value for this property is `false`. + +This property cannot be changed dynamically during the gesture's lifecycle. If it changes in runtime the application will crash. + +```jsx +const gesture = usePanGesture({ + // highlight-next-line + disableReanimated: true, + + onUpdate: () => { + console.log('Panning'); + }, +}); +``` + +#### runOnJS + +The `runOnJS` property allows you to dynamically control whether callbacks are executed on the JS thread or the UI thread. When set to `true`, callbacks will run on the JS thread. Setting it to `false` will execute them on the UI thread. Default value for this property is `false`. + +This property can be changed dynamically throughout the gesture's lifecycle. + +```jsx +import React from 'react'; +import { View } from 'react-native'; +import { + GestureDetector, + GestureHandlerRootView, + usePanGesture, +} from 'react-native-gesture-handler'; +import Animated, { useSharedValue } from 'react-native-reanimated'; + +export default function App() { + // highlight-next-line + const shouldRunOnJS = useSharedValue(false); + + const panGesture = usePanGesture({ + onUpdate: () => { + console.log( + globalThis.__RUNTIME_KIND === 2 + ? 'Running on UI thread' + : 'Running on JS thread' + ); + }, + onDeactivate: () => { + shouldRunOnJS.value = !shouldRunOnJS.value; + }, + // highlight-next-line + runOnJS: shouldRunOnJS, + }); + + return ( + + + + + + + + ); +} +``` + +### Migrating components relying on view hierarchy + +Certain components, such as `SVG`, depend on the view hierarchy to function correctly. In Gesture Handler 3, `GestureDetector` disrupts these hierarchies. To resolve this issue, two new detectors have been introduced: `InterceptingGestureDetector` and `VirtualGestureDetector`. + +`InterceptingGestureDetector` functions similarly to the `GestureDetector`, but it can also act as a proxy for `VirtualGestureDetector` within its component subtree. Because it can be used solely to establish the context for virtual detectors, the `gesture` property is optional. + +`VirtualGestureDetector` is similar to the `GestureDetector` from RNGH2. Because it is not a host component, it does not interfere with the host view hierarchy. This allows you to attach gestures without disrupting functionality that depends on it. + +:::danger Detectors order +`VirtualGestureDetector` has to be a descendant of `InterceptingGestureDetector`. +::: + +#### Migrating SVG + +In Gesture Handler 2 it was possible to use `GestureDetector` directly on `SVG`. In Gesture Handler 3, the correct way to interact with `SVG` is to use `InterceptingGestureDetector` and `VirtualGestureDetector`. + +Old API: +```js +import { GestureDetector } from 'react-native-gesture-handler'; + + + + + + + + +``` + +New API: +```js +import { InterceptingGestureDetector, VirtualGestureDetector } from 'react-native-gesture-handler'; + + + + + + + + +``` + +### Old components + +When the code using the component relies on the APIs that are no longer available on the components in Gesture Handler 3 (like `waitFor`, `simultaneousWith`, `blocksHandler`, `onHandlerStateChange`, `onGestureEvent` props), it cannot be easily migrated in isolation. In this case update the imports to the Legacy version of the component, and inform the user that the dependencies need to be migrated first. + +If the migration is possible, use the ask questions tool to clarify the user intent unless clearly stated beforehand: should the components be using the new implementation (no `Legacy` prefix when imported), or should they revert to the old implementation (`Legacy` prefix when imported)? + +#### Buttons + +Don't suggest replacing buttons from Gesture Handler with components from React Native. + +The implementation of buttons has been updated, resolving most button-related issues. They have also been internally rewritten to utilize the new hook API. The original button components are still accessible but have been renamed with the prefix `Legacy`, e.g., `RectButton` is now available as `LegacyRectButton`. + +Although the legacy JS implementation of the buttons is still available, they also use the new host component internally. + +Legacy buttons + +| RNGH2 | RNGH3 | +| --------------------- | --------------------------- | +| `RawButton` | `LegacyRawButton` | +| `BaseButton` | `LegacyBaseButton` | +| `RectButton` | `LegacyRectButton` | +| `BorderlessButton` | `LegacyBorderlessButton` | + +#### Other components + +Other components have also been internally rewritten using the new hook API but are exported under their original names, so no changes are necessary on your part. However, if you need to use the previous implementation for any reason, the old components are also available and are prefixed with `Legacy`, e.g., `ScrollView` is now available as `LegacyScrollView`. + +Legacy components + +| RNGH2 | RNGH3 | +| --------------------- | --------------------------- | +| `ScrollView` | `LegacyScrollView` | +| `FlatList` | `LegacyFlatList` | +| `RefreshControl` | `LegacyRefreshControl` | +| `Switch` | `LegacySwitch` | +| `TextInput` | `LegacyTextInput` | +| `DrawerLayoutAndroid` | `LegacyDrawerLayoutAndroid` | + + +### Replaced types + +Most of the types used in the old API, like `TapGesture`, are still present in Gesture Handler 3. However, they are now used in new hook API. Types for old API now have `Legacy` prefix, e.g. `TapGesture` becomes `LegacyTapGesture`. + +Legacy types + +| RNGH2 | RNGH3 | +| ----------------------- | ----------------------------- | +| `PanGesture` | `LegacyPanGesture` | +| `TapGesture` | `LegacyTapGesture` | +| `LongPressGesture` | `LegacyLongPressGesture` | +| `RotationGesture` | `LegacyRotationGesture` | +| `PinchGesture` | `LegacyPinchGesture` | +| `FlingGesture` | `LegacyFlingGesture` | +| `HoverGesture` | `LegacyHoverGesture` | +| `NativeGesture` | `LegacyNativeGesture` | +| `ManualGesture` | `LegacyManualGesture` | +| `ForceTouchGesture` | `LegacyForceTouchGesture` | +| | | +| `ComposedGesture` | `LegacyComposedGesture` | +| `RaceGesture` | `LegacyRaceGesture` | +| `SimultaneousGesture` | `LegacySimultaneousGesture` | +| `ExclusiveGesture` | `LegacyExclusiveGesture` | +| | | +| `RawButtonProps` | `LegacyRawButtonProps` | +| `BaseButtonProps` | `LegacyBaseButtonProps` | +| `RectButtonProps` | `LegacyRectButtonProps` | +| `BorderlessButtonProps` | `LegacyBorderlessButtonProps` | From c076d3b379e5a24c06f67f4ba39f18adb307098c Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 4 Feb 2026 12:29:42 +0100 Subject: [PATCH 2/9] Review --- .cursor/skills/gesture-handler-3-migration/SKILL.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.cursor/skills/gesture-handler-3-migration/SKILL.md b/.cursor/skills/gesture-handler-3-migration/SKILL.md index 1b1f777db2..37944bf561 100644 --- a/.cursor/skills/gesture-handler-3-migration/SKILL.md +++ b/.cursor/skills/gesture-handler-3-migration/SKILL.md @@ -5,7 +5,7 @@ description: Migrates files containing React Native components which use the Rea # Migrate to Gesture Handler 3 -This skill goes over the components using the Gesture Handler builder-based API updates it to use the new hook-based API. It also handles updating types and components which may have changed in the new version. +This skill scans React Native components that use the Gesture Handler builder-based API and updates them to use the new hook-based API. It also updates related types and components to adapt to the new version. ## When to Use @@ -43,7 +43,7 @@ const gesture = usePanGesture({ }, minDistance: 25, }); -```` +``` `ForceTouch` gesture is not available in hooks API. If it's still being used, leave it as is. @@ -132,7 +132,7 @@ const gesture = usePanGesture({ In Gesture Handler 3, `stateManager` is no longer passed to `TouchEvent` callbacks. Instead, you should use the global `GestureStateManager`. -`GestureStateManager` provides methods for iperative state management: +`GestureStateManager` provides methods for imperative state management: - .begin(handlerTag: number) - .activate(handlerTag: number) - .deactivate(handlerTag: number) (.end() in the old API) @@ -301,7 +301,7 @@ const gesture = useTapGesture({ ```jsx const callback = useCallback(() => { // highlight-next-line - 'worklet;'; + 'worklet'; console.log(_WORKLET); }, []); From eeaafede18ebadf41b0e795f0923aac995b6b127 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 4 Feb 2026 12:34:25 +0100 Subject: [PATCH 3/9] Remove docusaurus syntax --- .cursor/skills/gesture-handler-3-migration/SKILL.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.cursor/skills/gesture-handler-3-migration/SKILL.md b/.cursor/skills/gesture-handler-3-migration/SKILL.md index 37944bf561..02b18d0a79 100644 --- a/.cursor/skills/gesture-handler-3-migration/SKILL.md +++ b/.cursor/skills/gesture-handler-3-migration/SKILL.md @@ -445,9 +445,7 @@ Certain components, such as `SVG`, depend on the view hierarchy to function corr `VirtualGestureDetector` is similar to the `GestureDetector` from RNGH2. Because it is not a host component, it does not interfere with the host view hierarchy. This allows you to attach gestures without disrupting functionality that depends on it. -:::danger Detectors order -`VirtualGestureDetector` has to be a descendant of `InterceptingGestureDetector`. -::: +**Warning:** `VirtualGestureDetector` has to be a descendant of `InterceptingGestureDetector`. #### Migrating SVG From 6d8b16ffeacedfb1d36f4423257ee67196710e81 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 4 Feb 2026 12:52:09 +0100 Subject: [PATCH 4/9] Update types and state fields in events --- .../gesture-handler-3-migration/SKILL.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.cursor/skills/gesture-handler-3-migration/SKILL.md b/.cursor/skills/gesture-handler-3-migration/SKILL.md index 02b18d0a79..a9fc8f40b0 100644 --- a/.cursor/skills/gesture-handler-3-migration/SKILL.md +++ b/.cursor/skills/gesture-handler-3-migration/SKILL.md @@ -128,6 +128,30 @@ const gesture = usePanGesture({ }); ``` +The `state` and `oldState` properties are not available on events in the hook-based API. Instead rely on which callback has been invoked: +- `onBegin`: `UNDETERMINED` -> `BEGAN` +- `onActivate`: `BEGAN` -> `ACTIVE` +- `onDeactivate`: `ACTIVE` -> `END`/`FAILED`/`CANCELLED` +- `onFinalize`: `BEGAN`/`ACTIVE` -> `END`/`FAILED`/`CANCELLED` (always called after `onDeactivate`) + +(`onBegin`, `onFinalize`) and (`onActivate`, `onDeactivate`) are complementary - if the first one is called, the second one is guaranteed to be called in the future. + +To determine whether the gesture has ended or failed, check the `didSucceed` variable (second argument passed to the `onDeactivate` and `onFinalize`). + +Due to this change, all callbacks of a gesture are using the same type: + +| Hook | Event type | +| --------- | -------------- | +| `usePanGesture()` | `PanGestureEvent` | +| `useTapGesture()` | `TapGestureEvent` | +| `useLongPressGesture()` | `LongPressGestureEvent` | +| `useRotationGesture()` | `RotationGestureEvent` | +| `usePinchGesture()` | `PinchGestureEvent` | +| `useFlingGesture()` | `FlingGestureEvent` | +| `useHoverGesture()` | `HoverGestureEvent` | +| `useNativeGesture()` | `RotationGestureEvent` | +| `useManualGesture()` | `ManualGestureEvent` | + #### StateManager In Gesture Handler 3, `stateManager` is no longer passed to `TouchEvent` callbacks. Instead, you should use the global `GestureStateManager`. From 5f89fe57c2aad6de561c975f9478c2b3379885c8 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 4 Feb 2026 12:57:15 +0100 Subject: [PATCH 5/9] Add explcit migration steps --- .../skills/gesture-handler-3-migration/SKILL.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.cursor/skills/gesture-handler-3-migration/SKILL.md b/.cursor/skills/gesture-handler-3-migration/SKILL.md index a9fc8f40b0..5a06f8d57f 100644 --- a/.cursor/skills/gesture-handler-3-migration/SKILL.md +++ b/.cursor/skills/gesture-handler-3-migration/SKILL.md @@ -17,6 +17,20 @@ This skill scans React Native components that use the Gesture Handler builder-ba Use the instructions below to correctly replace all legacy APIs with the modern ones. +1. Identify all imports from 'react-native-gesture-handler' +2. For each `Gesture.X()` call, replace with corresponding `useXGesture()` hook +3. Replace `Gesture` import with imports for the used hooks +4. Convert builder method chains to configuration objects +5. Update callback names (onStart → onActivate, etc.) +6. Replace composed gestures with relation hooks. Keep rules of hooks in mind +7. Update GestureDetector usage if SVG is involved to Intercepting/Virtual GestureDetector +8. Update usage of compoenent imported from 'react-native-gesture-handler' according to "Legacy components" section + +## Common Migration Errors +- Mixing old builder API with hooks in same GestureDetector -> Use one API consistently +- Referencing gesture object inside its own callbacks -> Use `event.handlerTag` instead +- Using `onChange` -> Move logic to `onUpdate` and use change* properties from event + ### Migrating gestures The most important change brought by the Gesture Handler 3 is the new hooks API. Migration is pretty straightforward. Instead of calling builder methods, everything is passed as a configuration object. @@ -501,7 +515,7 @@ import { InterceptingGestureDetector, VirtualGestureDetector } from 'react-nativ ``` -### Old components +### Legacy components When the code using the component relies on the APIs that are no longer available on the components in Gesture Handler 3 (like `waitFor`, `simultaneousWith`, `blocksHandler`, `onHandlerStateChange`, `onGestureEvent` props), it cannot be easily migrated in isolation. In this case update the imports to the Legacy version of the component, and inform the user that the dependencies need to be migrated first. From dcc83ecf3a59319865c4bc285194bcc3b79a018b Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 4 Feb 2026 13:20:30 +0100 Subject: [PATCH 6/9] Update event types --- .cursor/skills/gesture-handler-3-migration/SKILL.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.cursor/skills/gesture-handler-3-migration/SKILL.md b/.cursor/skills/gesture-handler-3-migration/SKILL.md index 5a06f8d57f..5f5ce6d012 100644 --- a/.cursor/skills/gesture-handler-3-migration/SKILL.md +++ b/.cursor/skills/gesture-handler-3-migration/SKILL.md @@ -166,6 +166,14 @@ Due to this change, all callbacks of a gesture are using the same type: | `useNativeGesture()` | `RotationGestureEvent` | | `useManualGesture()` | `ManualGestureEvent` | +The exception to this is touch events: +- `onTouchesDown` +- `onTouchesUp` +- `onTouchesMove` +- `onTouchesCancel` + +Where each callback receives `GestureTouchEvent` regardless of the hook used. + #### StateManager In Gesture Handler 3, `stateManager` is no longer passed to `TouchEvent` callbacks. Instead, you should use the global `GestureStateManager`. From 31133b05b68c884940b8503994369ba1717a781d Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 5 Feb 2026 12:39:20 +0100 Subject: [PATCH 7/9] Optimize for token count --- .../gesture-handler-3-migration/SKILL.md | 493 ++---------------- 1 file changed, 34 insertions(+), 459 deletions(-) diff --git a/.cursor/skills/gesture-handler-3-migration/SKILL.md b/.cursor/skills/gesture-handler-3-migration/SKILL.md index 5f5ce6d012..633b7af8d1 100644 --- a/.cursor/skills/gesture-handler-3-migration/SKILL.md +++ b/.cursor/skills/gesture-handler-3-migration/SKILL.md @@ -26,145 +26,31 @@ Use the instructions below to correctly replace all legacy APIs with the modern 7. Update GestureDetector usage if SVG is involved to Intercepting/Virtual GestureDetector 8. Update usage of compoenent imported from 'react-native-gesture-handler' according to "Legacy components" section -## Common Migration Errors -- Mixing old builder API with hooks in same GestureDetector -> Use one API consistently -- Referencing gesture object inside its own callbacks -> Use `event.handlerTag` instead -- Using `onChange` -> Move logic to `onUpdate` and use change* properties from event - ### Migrating gestures -The most important change brought by the Gesture Handler 3 is the new hooks API. Migration is pretty straightforward. Instead of calling builder methods, everything is passed as a configuration object. -Unless specified, the names and types of callbacks and properties remain unchanged. - -Old API: -```js -import { Gesture } from 'react-native-gesture-handler'; - -const gesture = Gesture.Pan() - .onBegin(() => { - console.log('Pan!'); - }) - .minDistance(25); -``` - -New API: -```js -import { usePanGesture } from 'react-native-gesture-handler'; - -const gesture = usePanGesture({ - onBegin: () => { - console.log('Pan!'); - }, - minDistance: 25, -}); -``` - -`ForceTouch` gesture is not available in hooks API. If it's still being used, leave it as is. - -All gestures from the old API and their counterparts in the new one: +All hook gestures have their counterparts in the builder API: `Gesture.X()` becomes `useXGesture(config)`. The methods are now config object fields with the same name as the relevant builder methods, unless specified otherwise. -| RNGH2 | RNGH3 | -| ---------------------- | ----------------------- | -| `Gesture.Pan()` | `usePanGesture()` | -| `Gesture.Tap()` | `useTapGesture()` | -| `Gesture.LongPress()` | `useLongPressGesture()` | -| `Gesture.Rotation()` | `useRotationGesture()` | -| `Gesture.Pinch()` | `usePinchGesture()` | -| `Gesture.Fling()` | `useFlingGesture()` | -| `Gesture.Hover()` | `useHoverGesture()` | -| `Gesture.Native()` | `useNativeGesture()` | -| `Gesture.Manual()` | `useManualGesture()` | -| `Gesture.ForceTouch()` | *Not available in hook API* | +The exception to thait is `esture.ForceTouch` which DOES NOT have a counterpart in the hook API. #### Callback changes In Gesture Handler 3 some of the callbacks were renamed, namely: - -| RNGH2 | RNGH3 | -| --------- | -------------- | -| `onStart` | `onActivate` | -| `onEnd` | `onDeactivate` | -| `onTouchesCancelled` | `onTouchesCancel` | - -Old API: -```js -import { Gesture } from 'react-native-gesture-handler'; - -const gesture = Gesture.Pan() - .onStart(() => { - console.log('Pan activated!'); - }) - .onEnd(() => { - console.log('Pan deactivated!'); - }) - .onTouchesCancelled(() => { - console.log('Touches canceled!'); - }); -``` - -New API: -```js -import { usePanGesture } from 'react-native-gesture-handler'; - -const gesture = usePanGesture({ - onActivate: () => { - console.log('Pan activated!'); - }, - onDeactivate: () => { - console.log('Pan deactivated!'); - }, - onTouchesCancel: () => { - console.log('Touches canceled!'); - }, -}); -``` +- `onStart` -> `onActivate` +- `onEnd` -> `onDeactivate` +- `onTouchesCancelled` -> `onTouchesCancel` In the hooks API `onChange` is no longer available. Instead the `*change*` properties were moved to the event available inside `onUpdate`. -Old API: -```js -import { Gesture } from 'react-native-gesture-handler'; - -const gesture = Gesture.Pan() - .onChange((event) => { - console.log(event.changeX, event.changeY); - }) -``` - -New API: -```js -import { usePanGesture } from 'react-native-gesture-handler'; - -const gesture = usePanGesture({ - onUpdate: (event) => { - console.log(event.changeX, event.changeY); - } -}); -``` - -The `state` and `oldState` properties are not available on events in the hook-based API. Instead rely on which callback has been invoked: -- `onBegin`: `UNDETERMINED` -> `BEGAN` -- `onActivate`: `BEGAN` -> `ACTIVE` -- `onDeactivate`: `ACTIVE` -> `END`/`FAILED`/`CANCELLED` -- `onFinalize`: `BEGAN`/`ACTIVE` -> `END`/`FAILED`/`CANCELLED` (always called after `onDeactivate`) - -(`onBegin`, `onFinalize`) and (`onActivate`, `onDeactivate`) are complementary - if the first one is called, the second one is guaranteed to be called in the future. - -To determine whether the gesture has ended or failed, check the `didSucceed` variable (second argument passed to the `onDeactivate` and `onFinalize`). - -Due to this change, all callbacks of a gesture are using the same type: - -| Hook | Event type | -| --------- | -------------- | -| `usePanGesture()` | `PanGestureEvent` | -| `useTapGesture()` | `TapGestureEvent` | -| `useLongPressGesture()` | `LongPressGestureEvent` | -| `useRotationGesture()` | `RotationGestureEvent` | -| `usePinchGesture()` | `PinchGestureEvent` | -| `useFlingGesture()` | `FlingGestureEvent` | -| `useHoverGesture()` | `HoverGestureEvent` | -| `useNativeGesture()` | `RotationGestureEvent` | -| `useManualGesture()` | `ManualGestureEvent` | +All callbacks of a gesture are now using the same type: +- `usePanGesture()` -> `PanGestureEvent` +- `useTapGesture()` -> `TapGestureEvent` +- `useLongPressGesture()` -> `LongPressGestureEvent` +- `useRotationGesture()` -> `RotationGestureEvent` +- `usePinchGesture()` -> `PinchGestureEvent` +- `useFlingGesture()` -> `FlingGestureEvent` +- `useHoverGesture()` -> `HoverGestureEvent` +- `useNativeGesture()` -> `RotationGestureEvent` +- `useManualGesture()` -> `ManualGestureEvent` The exception to this is touch events: - `onTouchesDown` @@ -190,113 +76,35 @@ In Gesture Handler 3, `stateManager` is no longer passed to `TouchEvent` callbac Callback definitions CANNOT reference the gesture that's being defined. In this scenario use events to get access to the handler tag. -Old API: -```js -import { Gesture } from 'react-native-gesture-handler'; - -const manual = Gesture.Manual().onTouchesDown((e, stateManager) => { - stateManager.activate(); -}); -``` - -New API: -```js -import { useManualGesture, GestureStateManager } from 'react-native-gesture-handler'; - -const manual = useManualGesture({ - onTouchesDown: (e) => { - GestureStateManager.activate(e.handlerTag); - }, -}); -``` - ### Migrating relations #### Composed gestures -Previously, composed gestures were created using `Gesture` object. In Gesture Handler 3, relations are set up using relation hooks. - -Old API: -```js -import { Gesture } from 'react-native-gesture-handler'; - -const pan1 = Gesture.Pan(); -const pan2 = Gesture.Pan(); - -const gesture = Gesture.Simultaneous(pan1, pan2); -``` - -New API: -```js -import { usePanGesture, useSimultaneousGestures } from 'react-native-gesture-handler'; - -const pan1 = usePanGesture({}); -const pan2 = usePanGesture({}); - -const gesture = useSimultaneousGestures(pan1, pan2); -``` +`Gesture.Simultaneous(gesture1, gesture2);` becomes `useSimultaneousGestures(pan1, pan2);` All relations from the old API and their counterparts in the new one: - -| RNGH2 | RNGH3 | -| ----------------------------------------- | --------------------------- | -| `Gesture.Race()` | `useCompetingGestures()` | -| `Gesture.Simultaneous()` | `useSimultaneousGestures()` | -| `Gesture.Exclusive()` | `useExclusiveGestures()` | - +- `Gesture.Race()` -> `useCompetingGestures()` +- `Gesture.Simultaneous()` -> `useSimultaneousGestures()` +- `Gesture.Exclusive()` -> `useExclusiveGestures()` #### Cross components relations properties -Properties used to define cross-components interactions were renamed. - -Old API: -```js -import { Gesture } from 'react-native-gesture-handler'; - -const pan1 = Gesture.Pan(); -const pan2 = - Gesture.Pan().requireExternalGestureToFail(pan1); -``` - -New API: -```js -import { usePanGesture } from 'react-native-gesture-handler'; - -const pan1 = usePanGesture({}); -const pan2 = usePanGesture({ - requireToFail: pan1, -}); -``` - -Full changes are as follows: - -| RNGH2 | RNGH3 | -| ----------------------------------------- | --------------------------- | -| `.simultaneousWithExternalGesture` | `simultaneousWith:` | -| `.requireExternalGestureToFail` | `requireToFail:` | -| `.blocksExternalGesture` | `block:` | +Properties used to define cross-components interactions were renamed: +- `.simultaneousWithExternalGesture` -> `simultaneousWith:` +- `.requireExternalGestureToFail` -> `requireToFail:` +- `.blocksExternalGesture` -> `block:` ### GestureDetector -The `GestureDetector` is a key component of `react-native-gesture-handler`. It supports gestures created either using the hooks API or the builder pattern (but those cannot be mixed, it's either or). Additionally, it allows for the recognition of multiple gestures through gesture composition. `GestureDetector` interacts closely with `Reanimated`. - -#### Reusing Gestures +The `GestureDetector` is a key component of `react-native-gesture-handler`. It supports gestures created either using the hooks API or the builder pattern (but those cannot be mixed, it's either or). Using the same instance of a gesture across multiple Gesture Detectors may result in undefined behavior. ### Integration with Reanimated -Worklets' Babel plugin is setup in a way that automatically marks callbacks passed to gestures in the configuration chain as worklets. This means that you don't need to add a `'worklet';` directive at the beginning of the functions. Here is an example that will be automatically workletized: +Worklets' Babel plugin is setup in a way that automatically marks callbacks passed to gestures in the configuration chain as worklets. This means that you don't need to add a `'worklet';` directive at the beginning of the functions. -```jsx -const gesture = useTapGesture({ - onBegin: () => { - console.log(_WORKLET); - }, -}); -``` - -This one will not be workletized because the callback is defined outside of the gesture object: +This will not be workletized because the callback is defined outside of the gesture object: ```jsx const callback = () => { @@ -308,180 +116,25 @@ const gesture = useTapGesture({ }); ``` -It also won't work when wrapped with hooks like `useCallback` or `useMemo`, e.g: - -```jsx -const callback = useCallback(() => { - console.log(_WORKLET); -}, []); - -const gesture = useTapGesture({ - onBegin: callback, -}); -``` - -This one will not be workletized as well, due to the Worklet's plugin limitation - the callback cannot be wrapped by any other higher order function to be workletized: - -```jsx -const gesture = useTapGesture({ - onBegin: useCallback(() => { - console.log(_WORKLET); - }, []), -}); -``` - -In the above cases, you should add a `"worklet";` directive at the beginning of the callbacks, like so: - -```jsx -const callback = () => { - // highlight-next-line - 'worklet'; - console.log(_WORKLET); -}; - -const gesture = useTapGesture({ - onBegin: callback, -}); -``` - -```jsx -const callback = useCallback(() => { - // highlight-next-line - 'worklet'; - console.log(_WORKLET); -}, []); - -const gesture = useTapGesture({ - onBegin: callback, -}); -``` +The callback wrapped by any other higher order function will not be workletized: ```jsx const gesture = useTapGesture({ onBegin: useCallback(() => { - 'worklet'; console.log(_WORKLET); }, []), }); ``` -### Using SharedValue in gesture config - -RNGH3 allows to pass `SharedValue` to gestures' configurations. This allows to react to configuration changes without unnecessary rerenders. For example: - -```js -import * as React from 'react'; -import { Animated } from 'react-native'; -import { - GestureHandlerRootView, - GestureDetector, - useTapGesture, -} from 'react-native-gesture-handler'; -import { useSharedValue } from 'react-native-reanimated'; - -export default function App() { - // highlight-next-line - const taps = useSharedValue(2); - - const gesture = useTapGesture({ - // highlight-next-line - numberOfTaps: taps, - onDeactivate: () => { - taps.value += 1; - }, - }); - - return ( - - - - - - ); -} -``` +In the above cases, you should add a `"worklet";` directive as the first line of the callback. ### Disabling Reanimated -Gestures created with the hook API have `Reanimated` integration enabled by default (if it's installed), meaning all callbacks are executed on the UI thread. There are two methods available to disable this behavior for a specific gesture. - -#### disableReanimated - -When `disableReanimated` is set to `true` in the gesture configuration, `Reanimated` integration will be completely turned off for that gesture throughout its entire lifecycle. This setting eliminates all interaction points with `Reanimated`, thereby reducing any potential overhead. Default value for this property is `false`. - -This property cannot be changed dynamically during the gesture's lifecycle. If it changes in runtime the application will crash. - -```jsx -const gesture = usePanGesture({ - // highlight-next-line - disableReanimated: true, - - onUpdate: () => { - console.log('Panning'); - }, -}); -``` +Gestures created with the hook API have `Reanimated` integration enabled by default (if it's installed), meaning all callbacks are executed on the UI thread. #### runOnJS -The `runOnJS` property allows you to dynamically control whether callbacks are executed on the JS thread or the UI thread. When set to `true`, callbacks will run on the JS thread. Setting it to `false` will execute them on the UI thread. Default value for this property is `false`. - -This property can be changed dynamically throughout the gesture's lifecycle. - -```jsx -import React from 'react'; -import { View } from 'react-native'; -import { - GestureDetector, - GestureHandlerRootView, - usePanGesture, -} from 'react-native-gesture-handler'; -import Animated, { useSharedValue } from 'react-native-reanimated'; - -export default function App() { - // highlight-next-line - const shouldRunOnJS = useSharedValue(false); - - const panGesture = usePanGesture({ - onUpdate: () => { - console.log( - globalThis.__RUNTIME_KIND === 2 - ? 'Running on UI thread' - : 'Running on JS thread' - ); - }, - onDeactivate: () => { - shouldRunOnJS.value = !shouldRunOnJS.value; - }, - // highlight-next-line - runOnJS: shouldRunOnJS, - }); - - return ( - - - - - - - - ); -} -``` +The `runOnJS` property allows you to dynamically control whether callbacks are executed on the JS thread or the UI thread. When set to `true`, callbacks will run on the JS thread. Setting it to `false` will execute them on the UI thread. Default value is `false`. ### Migrating components relying on view hierarchy @@ -497,96 +150,18 @@ Certain components, such as `SVG`, depend on the view hierarchy to function corr In Gesture Handler 2 it was possible to use `GestureDetector` directly on `SVG`. In Gesture Handler 3, the correct way to interact with `SVG` is to use `InterceptingGestureDetector` and `VirtualGestureDetector`. -Old API: -```js -import { GestureDetector } from 'react-native-gesture-handler'; - - - - - - - - -``` - -New API: -```js -import { InterceptingGestureDetector, VirtualGestureDetector } from 'react-native-gesture-handler'; - - - - - - - - -``` - ### Legacy components When the code using the component relies on the APIs that are no longer available on the components in Gesture Handler 3 (like `waitFor`, `simultaneousWith`, `blocksHandler`, `onHandlerStateChange`, `onGestureEvent` props), it cannot be easily migrated in isolation. In this case update the imports to the Legacy version of the component, and inform the user that the dependencies need to be migrated first. If the migration is possible, use the ask questions tool to clarify the user intent unless clearly stated beforehand: should the components be using the new implementation (no `Legacy` prefix when imported), or should they revert to the old implementation (`Legacy` prefix when imported)? -#### Buttons - -Don't suggest replacing buttons from Gesture Handler with components from React Native. - -The implementation of buttons has been updated, resolving most button-related issues. They have also been internally rewritten to utilize the new hook API. The original button components are still accessible but have been renamed with the prefix `Legacy`, e.g., `RectButton` is now available as `LegacyRectButton`. - -Although the legacy JS implementation of the buttons is still available, they also use the new host component internally. - -Legacy buttons - -| RNGH2 | RNGH3 | -| --------------------- | --------------------------- | -| `RawButton` | `LegacyRawButton` | -| `BaseButton` | `LegacyBaseButton` | -| `RectButton` | `LegacyRectButton` | -| `BorderlessButton` | `LegacyBorderlessButton` | - -#### Other components - -Other components have also been internally rewritten using the new hook API but are exported under their original names, so no changes are necessary on your part. However, if you need to use the previous implementation for any reason, the old components are also available and are prefixed with `Legacy`, e.g., `ScrollView` is now available as `LegacyScrollView`. - -Legacy components +Don't suggest replacing buttons from Gesture Handler with components from React Native and vice versa. -| RNGH2 | RNGH3 | -| --------------------- | --------------------------- | -| `ScrollView` | `LegacyScrollView` | -| `FlatList` | `LegacyFlatList` | -| `RefreshControl` | `LegacyRefreshControl` | -| `Switch` | `LegacySwitch` | -| `TextInput` | `LegacyTextInput` | -| `DrawerLayoutAndroid` | `LegacyDrawerLayoutAndroid` | +The implementation of buttons has been updated, resolving most button-related issues. They have also been internally rewritten to utilize the new hook API. The legacy JS implementations of button components are still accessible but have been renamed with the prefix `Legacy`, e.g., `RectButton` is now available as `LegacyRectButton`. Those still use the new native component under the hood. +Other components have also been internally rewritten using the new hook API but are exported under their original names, so no changes are necessary on your part. However, if you need to use the previous implementation for any reason, the legacy components are also available and are prefixed with `Legacy`, e.g., `ScrollView` is now available as `LegacyScrollView`. ### Replaced types -Most of the types used in the old API, like `TapGesture`, are still present in Gesture Handler 3. However, they are now used in new hook API. Types for old API now have `Legacy` prefix, e.g. `TapGesture` becomes `LegacyTapGesture`. - -Legacy types - -| RNGH2 | RNGH3 | -| ----------------------- | ----------------------------- | -| `PanGesture` | `LegacyPanGesture` | -| `TapGesture` | `LegacyTapGesture` | -| `LongPressGesture` | `LegacyLongPressGesture` | -| `RotationGesture` | `LegacyRotationGesture` | -| `PinchGesture` | `LegacyPinchGesture` | -| `FlingGesture` | `LegacyFlingGesture` | -| `HoverGesture` | `LegacyHoverGesture` | -| `NativeGesture` | `LegacyNativeGesture` | -| `ManualGesture` | `LegacyManualGesture` | -| `ForceTouchGesture` | `LegacyForceTouchGesture` | -| | | -| `ComposedGesture` | `LegacyComposedGesture` | -| `RaceGesture` | `LegacyRaceGesture` | -| `SimultaneousGesture` | `LegacySimultaneousGesture` | -| `ExclusiveGesture` | `LegacyExclusiveGesture` | -| | | -| `RawButtonProps` | `LegacyRawButtonProps` | -| `BaseButtonProps` | `LegacyBaseButtonProps` | -| `RectButtonProps` | `LegacyRectButtonProps` | -| `BorderlessButtonProps` | `LegacyBorderlessButtonProps` | +Most of the types used in the builder API, like `TapGesture`, are still present in Gesture Handler 3. However, they are now used in new hook API. Types for builder API now have `Legacy` prefix, e.g. `TapGesture` becomes `LegacyTapGesture`. From 696d301202b6ab34bbca0d60783aa96271f7fea7 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 5 Feb 2026 12:43:29 +0100 Subject: [PATCH 8/9] Move to the repository root --- {.cursor/skills => skills}/gesture-handler-3-migration/SKILL.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {.cursor/skills => skills}/gesture-handler-3-migration/SKILL.md (100%) diff --git a/.cursor/skills/gesture-handler-3-migration/SKILL.md b/skills/gesture-handler-3-migration/SKILL.md similarity index 100% rename from .cursor/skills/gesture-handler-3-migration/SKILL.md rename to skills/gesture-handler-3-migration/SKILL.md From 61e35695308a3121de4c572bfcfa6c86ec968a6b Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 6 Feb 2026 15:37:14 +0100 Subject: [PATCH 9/9] Review --- skills/gesture-handler-3-migration/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/gesture-handler-3-migration/SKILL.md b/skills/gesture-handler-3-migration/SKILL.md index 633b7af8d1..6749d47e10 100644 --- a/skills/gesture-handler-3-migration/SKILL.md +++ b/skills/gesture-handler-3-migration/SKILL.md @@ -30,7 +30,7 @@ Use the instructions below to correctly replace all legacy APIs with the modern All hook gestures have their counterparts in the builder API: `Gesture.X()` becomes `useXGesture(config)`. The methods are now config object fields with the same name as the relevant builder methods, unless specified otherwise. -The exception to thait is `esture.ForceTouch` which DOES NOT have a counterpart in the hook API. +The exception to thait is `Gesture.ForceTouch` which DOES NOT have a counterpart in the hook API. #### Callback changes @@ -98,7 +98,7 @@ Properties used to define cross-components interactions were renamed: The `GestureDetector` is a key component of `react-native-gesture-handler`. It supports gestures created either using the hooks API or the builder pattern (but those cannot be mixed, it's either or). -Using the same instance of a gesture across multiple Gesture Detectors may result in undefined behavior. +Don't use the same instance of a gesture across multiple Gesture Detectors as it will lead to an undefined behavior. ### Integration with Reanimated