Skip to content

Add cancelsJSResponder prop to the gesture handlers#4094

Open
coado wants to merge 34 commits intomainfrom
prevent-recognizers
Open

Add cancelsJSResponder prop to the gesture handlers#4094
coado wants to merge 34 commits intomainfrom
prevent-recognizers

Conversation

@coado
Copy link
Copy Markdown
Contributor

@coado coado commented Apr 16, 2026

Description

Adds a per-gesture cancelsJSResponder?: boolean prop on both platforms to the v3 handler hooks. It defaults to true to preserve the behavior of cancelling RN JS responder system.

On Android it moves the root-view dispatch off onCancel onto new orchestrator callbacks onCancelJSResponderRequested and onCancelJSResponderReleased. The orchestrator only files the "released" callback when the last active handler with cancelsJSResponder: true finishes.

on iOS the RNRootViewGestureRecognizer checks the handler prop before notifying the delegate.

Test plan

Added new test example with multiple handlers that might or might not cancel the JS responder.

Simulator Screenshot - iPhone 16 Pro Max - 2026-04-17 at 12 59 42

Copilot AI review requested due to automatic review settings April 16, 2026 09:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new preventRecognizers configuration option to gesture handlers to control whether GH activation cancels React Native JS responders (with native iOS/Android plumbing + an example to demonstrate behavior).

Changes:

  • Added preventRecognizers to shared gesture config types/whitelists so it can flow from JS to native.
  • Implemented native behavior gates on iOS (root recognizer) and Android (orchestrator → root helper interception) with default true.
  • Added docs + a new common-app example to showcase responder cancellation differences.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/react-native-gesture-handler/src/v3/types/ConfigTypes.ts Adds preventRecognizers to common gesture config typing.
packages/react-native-gesture-handler/src/v3/hooks/utils/propsWhiteList.ts Allows the new config prop to pass through native config filtering.
packages/react-native-gesture-handler/src/v3/detectors/NativeDetector.tsx Minor formatting-only change near detector initialization.
packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector/utils.ts Adds preventRecognizers to allowed prop list for detector plumbing.
packages/react-native-gesture-handler/apple/RNRootViewGestureRecognizer.m iOS: only trigger RN responder cancellation when preventRecognizers is enabled.
packages/react-native-gesture-handler/apple/RNGestureHandler.mm iOS: default + config parsing for preventRecognizers.
packages/react-native-gesture-handler/apple/RNGestureHandler.h iOS: exposes preventRecognizers on the handler.
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt Android: intercept/cancel RN touch stream when requested by orchestrator.
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt Android: emits request/release callbacks based on handler activation + finish.
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt Android: stores/reads preventRecognizers from config with default true.
packages/docs-gesture-handler/docs/fundamentals/root-view.mdx Doc formatting cleanup (indentation/trailing whitespace).
packages/docs-gesture-handler/docs/fundamentals/gesture-detector.mdx Adds docs for preventRecognizers (but currently includes a broken tag and placement concerns).
apps/common-app/src/new_api/tests/rnResponderCancellation/index.tsx New example screen demonstrating RN responder cancellation vs. concurrent handling.
apps/common-app/src/new_api/index.tsx Registers the new example in the app’s examples list.
Comments suppressed due to low confidence (1)

packages/docs-gesture-handler/docs/fundamentals/gesture-detector.mdx:206

  • The closing tag for HeaderWithBadges was changed to </BadHeaderWithBadgesges>, which is not a valid component/tag and will break MDX rendering/build. Replace it with the correct </HeaderWithBadges> closing tag.
<HeaderWithBadges platforms={['web']}>
### touchAction
</HeaderWithBadges>


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/react-native-gesture-handler/apple/RNRootViewGestureRecognizer.m Outdated
Comment thread packages/docs-gesture-handler/docs/fundamentals/gesture-detector.mdx Outdated
@coado
Copy link
Copy Markdown
Contributor Author

coado commented Apr 17, 2026

@copilot review?

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 17, 2026

@copilot review?

Here's the review of the current state of the PR:

Overall: The implementation is solid. All 3 previously flagged issues are resolved. Here's a brief summary per area:

Android:

  • onCancelJSResponderRequested is correctly placed after the FAILED/CANCELLED early-return guard in makeActive — no stale shouldIntercept=true risk for already-finished handlers.
  • onCancelJSResponderReleased checks that no other active handler still has cancelsJSResponder=true before clearing shouldIntercept. ✓

iOS:

  • cancelsJSResponder property defaults to YES in resetConfig, read from config in updateConfig. ✓
  • canBePreventedByGestureRecognizer: gates the delegate call on handler.cancelsJSResponder. ✓

TypeScript:

  • Prop flows correctly: CommonGestureConfig (v3 types) → CommonConfig whitelist (v3) → ALLOWED_PROPS (v2 utils). ✓

Docs: cancelsJSResponder is documented in base-gesture-config.mdx (not under GestureDetector props). ✓

No issues found in the current state.

@coado coado changed the title [DRAFT] Add preventRecognizers prop to the gesture handlers Add preventRecognizers prop to the gesture handlers Apr 23, 2026
@coado coado marked this pull request as ready for review April 23, 2026 14:10
Copy link
Copy Markdown
Member

@j-piasecki j-piasecki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the title and description with the final prop name?

Also, I don't have an iOS device on hand, so can you check the following scenario?

There are two handlers and js recognizer on the same view. Gesture A with cancelsJSRecognizer: false, and a config that prevents it from activating (like minimumDistance: 100000 on pan). Gesture B has cancelsJSRecognizer: false and requires A to fail before it activates (useExclusiveGestures(A, B)), but should try to activate almost immediately (like pan with minimumDistance: 10). Now, in that scenario, does B trying to activate but getting blocked on A, cancel the JS recognizer, or not?

I've checked Android and there it works as expected (until A fails, the JS recognizer is not interrupted).

Comment thread packages/react-native-gesture-handler/src/v3/detectors/NativeDetector.tsx Outdated
Comment thread packages/docs-gesture-handler/docs/gestures/_shared/base-gesture-config.mdx Outdated
Comment thread packages/react-native-gesture-handler/apple/RNRootViewGestureRecognizer.m Outdated
@coado coado changed the title Add preventRecognizers prop to the gesture handlers Add cancelsJSResponder prop to the gesture handlers Apr 24, 2026
coado and others added 5 commits April 24, 2026 11:40
Copy link
Copy Markdown
Collaborator

@m-bert m-bert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like @j-piasecki caught everything

Comment thread apps/common-app/src/new_api/tests/rnResponderCancellation/index.tsx Outdated
Comment thread packages/docs-gesture-handler/docs/gestures/_shared/base-gesture-config.mdx Outdated
Comment thread packages/docs-gesture-handler/docs/gestures/_shared/base-gesture-config.mdx Outdated
Comment thread packages/docs-gesture-handler/docs/gestures/_shared/base-gesture-config.mdx Outdated
@coado coado force-pushed the prevent-recognizers branch from 3a3d592 to 9f24c36 Compare April 24, 2026 14:30
}

override fun onCancelJSResponderReleased(handler: GestureHandler) {
shouldIntercept = false
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to reset shouldIntercept here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the last active handler with cancelsJSReponder=true deactivates, this method is called so that RN touch events are no longer intercepted.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check how this behaves with iOS system recognizers? i.e., two simultaneous gestures: A with cancelsJSReponder=true, B with cancelsJSReponder=false, and a JS responder/native view. Both A and B activate, then A finishes while B stays active. Does the JS responder/native view work in this scenario?

I think this is the case where this would matter on Android, but it would also allow native components to start handling touch in addition to the JS responder. Ideally, both platforms would be aligned on this.

@coado
Copy link
Copy Markdown
Contributor Author

coado commented Apr 30, 2026

Can you update the title and description with the final prop name?

Also, I don't have an iOS device on hand, so can you check the following scenario?

There are two handlers and js recognizer on the same view. Gesture A with cancelsJSRecognizer: false, and a config that prevents it from activating (like minimumDistance: 100000 on pan). Gesture B has cancelsJSRecognizer: false and requires A to fail before it activates (useExclusiveGestures(A, B)), but should try to activate almost immediately (like pan with minimumDistance: 10). Now, in that scenario, does B trying to activate but getting blocked on A, cancel the JS recognizer, or not?

I've checked Android and there it works as expected (until A fails, the JS recognizer is not interrupted).

Not sure if you meant cancelsJSRecognizer: true on B, but in both cases seems like it works as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants