Repository navigation
Description
Description
On Android, a v3 gesture in a GestureDetector is cancelled when a re-render moves the detector's native view while the gesture is active, though the finger is still down. A drag-to-reorder list does exactly that: it moves the held row to its new place as the finger moves, and Fabric moves the row's view by removing it and adding it back. The gesture gets onTouchesCancelled and onDeactivate with canceled: true within a frame of the move.
Expected: the gesture stays active through the move and ends on release.
Actual: it is cancelled right after the move.
Cause: RNGestureHandlerDetectorView.onDetachedFromWindow calls detachAllHandlers(), and RNGestureHandlerRegistry.detachHandlerInternal cancels a handler whose view is set. A Fabric move removes the view and adds it back within one mount, so the detector is off the window only for that moment, but the cancel has already happened; on re-attach the handlers come back idle. The same screen with the v2 builder (Gesture.Pan()) keeps the drag, as v2 handlers aren't detached on a window detach. This is still the code on main.
We hit it in an app's board: cards in a FlatList, dragged by a handle with usePanGesture to reorder them live. Logcat from the pan's callbacks, three drags, each cancelled right after the first re-render that touched the held row:
# pick-up re-renders the held card dimmed, the list's scrolling off
03:33.663 onActivate
03:33.664 onUpdate y=716.1
03:33.680 onUpdate y=706.5
03:33.706 onTouchesCancelled
03:33.706 onDeactivate canceled=true
# pick-up collapses the list around a held section header
13:05.147 onActivate
13:05.147 onUpdate y=588.4
13:05.165 onUpdate y=581.7
13:05.183 onUpdate y=576.4
13:05.210 onTouchesCancelled
13:05.211 onDeactivate canceled=true
# nothing re-rendered on pick-up: the pan runs until the list first moves the held card
13:29.315 onActivate
13:29.315 onUpdate y=715.5
... (22 more updates, finger still moving)
13:29.697 card moved in the list
13:29.738 onTouchesCancelled
13:29.738 onDeactivate canceled=true
With the callbacks only logging (nothing re-rendered), the same drag is never cancelled.
iOS: not checked. RNGestureHandlerDetector also detaches its handlers in willMoveToWindow: with a nil window, so it may behave the same.
Minimal code:
function Row({ id, onGrab, onRelease }) {
const pan = usePanGesture({
runOnJS: true,
onActivate: () => onGrab(id),
onDeactivate: (e) => onRelease(e.canceled),
});
return (
<GestureDetector gesture={pan}>
<View style={{ height: 80, marginBottom: 12, backgroundColor: "navy" }} />
</GestureDetector>
);
}
export default function Repro() {
const [rows, setRows] = useState(["A", "B", "C", "D"]);
const onGrab = (id) => setRows((r) => [id, ...r.filter((x) => x !== id)]);
const onRelease = (canceled) => console.log("canceled:", canceled); // true on Android
return (
<FlatList
data={rows}
keyExtractor={(id) => id}
renderItem={({ item }) => (
<Row id={item} onGrab={onGrab} onRelease={onRelease} />
)}
/>
);
}Steps to reproduce
- Render the code above on Android (New Architecture).
- Drag a row that isn't at the top, sideways so the list doesn't scroll, and keep moving.
- The row moves to the top as the pan activates.
onDeactivatefires withcanceled: trueright after, while the finger is still down.
A link to a Gist, an Expo Snack or a link to a repository based on this template that reproduces the bug.
https://gist.github.com/roidayan1/6c9e3ebf4b5056d91806acd888a7ebca
Gesture Handler version
3.3.0 (the code is unchanged on main after 3.4.0)
React Native version
0.88.0
Platforms
Android
JavaScript runtime
Hermes
Workflow
Using Expo Prebuild or an Expo development build
Build type
Release mode
Device
Android emulator
Device model
Pixel emulator, Android 16 (API 36)
Acknowledgements
Yes
Activity
m-bert commentedon Oct 20, 2026
Hi @roidayan1! Thank you for reporting this issue!
This is expected in Gesture Handler 3.
GestureDetectoris now a host component (unless you're on the builder API), so when Fabric moves its view, the detector gets detached and its gestures are cancelled.Your case is exactly what
VirtualGestureDetectoris for - have you tried it? Wrap the list inInterceptingGestureDetectorand useVirtualGestureDetectorper row. This should letPansurvive reordering on Android.roidayan1 commentedon Oct 20, 2026
Thanks @m-bert, that's what we ended up doing:
InterceptingGestureDetectoraround the list andVirtualGestureDetectorper row, and the pan now survives the moves on Android. I'm closing #4609.A suggestion for the docs: the detector page doesn't say that a plain
GestureDetectorcancels an active gesture when Fabric moves its view, so a drag-to-reorder list built the obvious way breaks only on Android. A short note there, pointing toVirtualGestureDetectorfor rows that move, would save the next person the search.m-bert commentedon Oct 21, 2026
Good to hear that it works. What do you think about #4623? Is it something that you had in mind?
roidayan1 commentedon Oct 21, 2026
@m-bert Yes, that's exactly what I had in mind, thanks! The example matches our case one to one.
[Docs] Document gesture cancellation when a detector's view is reorde…