Fix fatal crash Cannot read property 'translationX' of undefined when a touch event is serialized without allTouches#4316
Open
huextrat wants to merge 2 commits into
Conversation
On Android, a touch event can be serialized without the `allTouches` key when its payload is lost in a race (e.g. rapid taps cancelling the gesture while a touch-up is being dispatched). `isTouchEvent` relies on `'allTouches' in event`, so such an event was misclassified as an update event and passed to the change event calculator, which crashed reading gesture-specific data (`Cannot read property 'translationX' of undefined`) inside a UI-thread worklet - a fatal error in production. - `eventHandler` now drops events that are neither state-change nor touch events and carry no `handlerData` instead of treating them as update events. - `getChangeEventCalculator` returns the event unchanged when `handlerData` is missing instead of calling the diff calculator with undefined. Valid events are unaffected; both guards are worklet-safe.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a hard crash in the v3 gesture event pipeline caused by a malformed Android touch event being serialized without the allTouches key, which led to misclassification as an update event and a UI-thread worklet exception.
Changes:
- Make the v3 JS event router defensively drop events that are neither state-change nor touch events and that also lack
handlerData, preventing the diff calculator from being called withundefined. - Make the v3 change calculator return the event unchanged when
handlerDatais missing (worklet-safe guard). - Fix Android touch event serialization to always include
changedTouchesandallToucheskeys (empty arrays when payloads are unavailable), matching iOS behavior; add Jest regression/unit tests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/react-native-gesture-handler/src/v3/hooks/utils/eventUtils.ts | Adds a worklet-safe guard in getChangeEventCalculator and relies on allTouches key presence for touch-event discrimination. |
| packages/react-native-gesture-handler/src/v3/hooks/callbacks/eventHandler.ts | Drops malformed non-touch/non-state-change events with missing handlerData instead of routing them to update handling. |
| packages/react-native-gesture-handler/src/tests/utils.test.tsx | Adds unit tests covering normal change-payload computation and the missing-handlerData regression case. |
| packages/react-native-gesture-handler/src/tests/api_v3.test.tsx | Adds an integration-style regression test ensuring malformed touch events don’t crash and don’t break subsequent valid updates. |
| packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/events/RNGestureHandlerTouchEvent.kt | Ensures changedTouches and allTouches are always serialized (empty arrays as fallback) to avoid JS-side misclassification. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
This PR was written with AI assistance (Claude), based on a production crash investigated from Sentry.
Description
Fixes a fatal, unhandled production crash in apps using the v3 API (
usePanGesture+GestureDetector) with Reanimated on Android (Fabric / New Architecture):Since the error is thrown inside a worklet running on the UI thread, it results in a native
CppExceptioncaught by theUncaughtExceptionHandler— a hard crash, not a JS redbox.Observed trigger: very fast repeated taps on a screen with a
GestureDetector+ pan gesture (reproduced in production on a low-end Android 15 device, but the underlying race is not device-specific).Root cause
The failure is a chain across the Android event serialization and the v3 event classification:
Kotlin —
GestureHandler.kt:dispatchTouchEvent()only guards onchangedTouchesPayload != null. IndispatchTouchUpEvent(),extractAllPointersData()runs before the changed pointer is re-added totrackedPointers. If the tracked pointers were already cleared at that point (e.g.cancelPointers()fired by a rapid succession of taps racing with the touch-up dispatch),allTouchesPayloadisnullwhilechangedTouchesPayloadis still populated — so the event is dispatched anyway.Kotlin —
RNGestureHandlerTouchEvent.kt: the serializer omitted the key entirely when the payload wasnull:→ a touch event can reach JS without the
allToucheskey. Note that iOS always serializesallTouches/changedTouchesas (possibly empty) arrays (RNGestureHandlerManager.mminitializes.allTouches = {}), so this was also a platform inconsistency.TS —
src/v3/hooks/utils/eventUtils.ts: touch events are discriminated by key presence:Key absent → the touch event is misclassified as an update event.
TS —
src/v3/hooks/callbacks/eventHandler.ts: the event is routed tohandleUpdateEvent()→getChangeEventCalculator()readscurrent.handlerData(undefinedfor a touch event) and passes it to the gesture'sdiffCalculator, which readscurrent.translationX→TypeErrorin a UI-thread worklet → fatal crash.Fix
JS (defensive, worklet-safe, no behavior change for valid events):
eventHandler()now drops events that are neither state-change events, nor touch events, nor carryhandlerData, instead of treating them as update events. (handlerDatais present on all well-formed update events on every platform: AndroidcreateNativeEventData, webGestureHandler.ts, andjestUtils.)getChangeEventCalculator()returns the event unchanged whenhandlerDataisundefinedinstead of calling the diff calculator with undefined data.Android (root cause):
RNGestureHandlerTouchEvent.createEventData()always serializesallTouchesandchangedTouches, falling back to empty arrays instead of omitting the keys — matching the iOS implementation.Test plan
src/__tests__/api_v3.test.tsx: fires a malformed touch event (noallTouches, nooldState, nohandlerData) through a pan gesture'sjsEventHandler. Without the JS fix it reproduces the exact production error (Cannot read properties of undefined (reading 'translationX')throughdiffCalculator); with the fix the event is dropped without invoking any callback, and subsequent valid update events still computechangeX/changeYcorrectly.getChangeEventCalculatorinsrc/__tests__/utils.test.tsx: change payload computed for well-formed events; event returned untouched whenhandlerDatais missing.yarn ts-checkclean,yarn lint:jsclean.spotlessCheckpasses, library compiles viaapps/basic-example(:react-native-gesture-handler:compileDebugKotlin— BUILD SUCCESSFUL).