Skip to content

Scan mount relations directly instead of rebuilding tag arrays - #4546

Open
teoarjun wants to merge 1 commit into
software-mansion:mainfrom
teoarjun:fix/mount-relation-scan
Open

teoarjun wants to merge 1 commit into
software-mansion:mainfrom
teoarjun:fix/mount-relation-scan

Conversation

@teoarjun

@teoarjun teoarjun commented Sep 25, 2026 •

Copy link
Copy Markdown

Description

Fixes #4540

shouldUpdateDetector resolved every relation entry through transformIntoHandlerTags on each gesture mount. That call runs toArray, map and filter, so each mount allocated three arrays per relation, for three relations, for every attached gesture of every mounted detector. The issue reports ~800ms of JS thread blocking when opening a sheet with ~50 pressables on a Pixel 7 Pro, with several hundred detectors mounted app-wide.

Most of that work could never match. Only refs can start pointing at a different handler after the detector attached, because current is filled in when the gesture they point at mounts. Gestures and numeric tags already carry their tag by then — and transformIntoHandlerTags mapped them to -1 and filtered them out anyway.

This walks the relation array directly and compares current.handlerTag, skipping entries that cannot resolve late. No allocations, and the impossible comparisons are gone.

It also fixes late-mounted relations on web, the secondary issue in the report: the web branch of transformIntoHandlerTags returns handler objects, which the caller compared against a numeric handlerTag, so the check was always false and those relations never re-attached. Comparing tags works on both platforms.

Behaviour for gesture objects and numeric tags is unchanged — they were already filtered out before reaching the comparison.

Test plan

New src/__tests__/useMountReactions.test.ts, run with Platform.OS mocked to web so it covers the branch that was broken:

  • a ref in blocksHandlers, requireToFail or simultaneousWith resolving on mount updates the detector — these three fail without the change, since the web path never matched
  • an unrelated gesture mounting does not update the detector
  • an unresolved ref (current: null) does not update the detector
  • gesture objects and raw tags do not update the detector, pinning the unchanged behaviour
  • an already-unmounted detector is not updated

Verified in packages/react-native-gesture-handler: yarn jest 185 passing across 22 suites, yarn ts-check clean, yarn lint:js clean on the changed files.

Not verified on a device or simulator — this environment has neither, so the Android and iOS builds and the example apps were not exercised.

`shouldUpdateDetector` resolved every relation entry through
`transformIntoHandlerTags` on each gesture mount. That call runs `toArray`,
`map` and `filter`, so each mount allocated three arrays per relation, for
three relations, for every attached gesture of every mounted detector.
Opening a sheet with ~50 pressables blocked the JS thread for ~800ms on a
Pixel 7 Pro with several hundred detectors mounted.

Most of that work could never match. Only refs can start pointing at a
different handler after the detector attached, because `current` is filled
in when the gesture they point at mounts. Gestures and numeric tags already
carry their tag by then, and `transformIntoHandlerTags` mapped them to -1
and filtered them out anyway.

Walk the relation array directly and compare `current.handlerTag`, skipping
the entries that cannot resolve late. No allocations, and the impossible
comparisons are gone.

This also fixes late-mounted relations on web, where they never re-attached:
the web branch of `transformIntoHandlerTags` returns handler objects, which
the caller compared against a numeric `handlerTag`, so the check was always
false. Comparing tags works on both platforms.

Fixes software-mansion#4540
Copilot AI lite review requested due to automatic review settings September 25, 2026 11:42
@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: c1d3801d-40d3-4323-82ed-c4c4bc7679c2

📥 Commits

Reviewing files that changed from the base of the PR and between e26231e and 6fc1188.

📒 Files selected for processing (2)
  • packages/react-native-gesture-handler/src/__tests__/useMountReactions.test.ts
  • packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector/useMountReactions.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • Gesture detectors now update when a referenced handler mounts in a relation such as blocking, failure requirements, or simultaneous recognition.
    • Unrelated mounts and relations that already contain handler objects or tags no longer trigger unnecessary detector updates.

Walkthrough

shouldUpdateDetector now checks relation refs directly for the mounted gesture’s handler tag. Web-platform tests cover matching refs across relation keys and cases where mounting must not update the detector.

Changes

Mount reaction relation checks

Layer / File(s) Summary
Match mounted handler tags through refs
packages/react-native-gesture-handler/src/handlers/gestures/GestureDetector/useMountReactions.ts, packages/react-native-gesture-handler/src/__tests__/useMountReactions.test.ts
shouldUpdateDetector skips relation entries without current and checks the current ref’s handlerTag. Web-platform tests cover matches in all three relation keys, unrelated or unresolved refs, gesture objects, raw tags, and unmounted detectors.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 6fc11

The relation check now matches mounted handlers through their refs, with tests covering matching and non-matching cases. No material merge-blocking risk is evident; normal checks remain appropriate.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning For #4540, the PR removes transformIntoHandlerTags and scans relation entries without allocating tag arrays. It correctly matches ref entries through current.handlerTag, including the web case, an… Handle string relation entries by resolving handlerIDToTag[entry] and comparing the result with gesture.handlerTag. Add a regression test for a matching string ID and a non-matching string ID.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: scanning mount relations directly instead of rebuilding tag arrays.
Out of Scope Changes check ✅ Passed The source change and the added tests directly implement #4540. The direct relation scan removes repeated tag-array allocation, preserves mount filtering, and covers the web late-mount path. No unrela…
Full details: Linked Issues check

Explanation

For #4540, the PR removes transformIntoHandlerTags and scans relation entries without allocating tag arrays. It correctly matches ref entries through current.handlerTag, including the web case, and adds tests for the three relation types and mount states. However, the linked issue identifies string IDs as entries that can resolve after attachment. The new loop skips every string entry, so a late-mounted relation that uses a string ID will not trigger updateDetector. The tests do not cover this required case.

  • Fix all pre-merge checks with AI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

One or more issues must be addressed before approval.

Review effort: Lite
Findings: None

What changed in this PR

Optimizes detector mount relation checks for issue #4540 by scanning refs directly without allocating intermediate arrays, while preserving relation behavior across platforms.

Changes:

  • Replaced transformIntoHandlerTags calls with allocation-free ref scanning.
  • Added web-focused tests for relation matching and lifecycle cases.
File Description
packages/​react-native-gesture-handler/​src/​handlers/​gestures/​GestureDetector/​useMountReactions.ts Updated as part of this pull request.
packages/​react-native-gesture-handler/​src/​__tests__/​useMountReactions.test.ts Updated as part of this pull request.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@teoarjun
teoarjun marked this pull request as ready for review September 25, 2026 11:49
@teoarjun

teoarjun commented Sep 26, 2026 •

Copy link
Copy Markdown
Author

Thanks for flagging the string-ID case — I looked into it specifically, and I don't think it reaches this path.

blocksHandlers, requireToFail and simultaneousWith are only written in two places: addDependency, whose parameter is typed Exclude<GestureRef, number> (a gesture object or a ref), and extendRelation during composition, which concatenates existing arrays of the same type. GestureRef has no string member.

More decisively, the attach-time resolver for these same three arrays — convertToHandlerTag in GestureDetector/utils.ts — handles exactly three cases:

if (typeof ref === 'number') return ref;
else if (ref instanceof BaseGesture) return ref.handlerTag;
else return ref.current?.handlerTag ?? -1;

There is no handlerIDToTag lookup there, so a string in one of these arrays would resolve to -1 and be filtered out at attach time as well — both before and after this change.

The handlerIDToTag lookup in transformIntoHandlerTags exists for its other caller, filterConfig, which serves the legacy simultaneousHandlers / waitFor props on the older component API. That is a separate surface and this PR doesn't touch it.

So the new scan deliberately mirrors convertToHandlerTag: numbers and gesture objects already carry their tag by the time the detector attaches, and only refs can resolve late. Happy to add string handling if there's a path into these arrays I've missed.

This branch has not been deployed

No deployments
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.

useMountReactions rescans relations that can never match, allocating 3 arrays per check

2 participants