Skip to content

refactor: replace makeMutable with createShareable - #4406

Open
tjzel wants to merge 5 commits into
mainfrom
@tjzel/drop-in-worklets
Open

refactor: replace makeMutable with createShareable#4406
tjzel wants to merge 5 commits into
mainfrom
@tjzel/drop-in-worklets

Conversation

@tjzel

@tjzel tjzel commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Description

Replaces makeMutable with createShareable.

Test plan

🚀

Copilot AI review requested due to automatic review settings August 7, 2026 13:18
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved compatibility between gesture handling, Reanimated, and Worklets.
    • Enhanced gesture event processing and cleanup on the UI runtime.
    • Added graceful fallback when required runtime capabilities are unavailable.
    • Improved reliability of UI runtime setup and error handling.
    • Ensured web builds avoid unsupported native runtime binding behavior.

Walkthrough

The wrapper loads Worklets independently from Reanimated and conditionally installs UI runtime bindings. Event handlers create shareable update maps when the required Worklets capabilities are available.

Changes

Worklets event handler integration

Layer / File(s) Summary
Typed Worklets loading and runtime setup
packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts, packages/react-native-gesture-handler/src/handlers/gestures/installUIRuntimeBindings.ts, packages/react-native-gesture-handler/src/handlers/gestures/installUIRuntimeBindings.web.ts, packages/react-native-gesture-handler/src/__tests__/RuntimeBindings.test.ts
The wrapper defines typed Worklets and Reanimated contracts, loads both modules independently, and installs UI runtime bindings only when Worklets is available. Native setup queues installation and clears the runtime holder after completion. The obsolete test is removed.
Shareable event map creation
packages/react-native-gesture-handler/src/v3/hooks/callbacks/lastUpdateEventMap.ts, packages/react-native-gesture-handler/src/v3/hooks/callbacks/lastUpdateEventMap.web.ts, packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts
Event handlers lazily create update maps through Worklets when runtime capabilities are available. The web implementation returns an empty shareable host. Cleanup uses the new shareable-map type.

Sequence Diagram(s)

sequenceDiagram
  participant EventHandler
  participant Worklets
  participant UIRuntime
  EventHandler->>Worklets: create shareable update map
  Worklets->>UIRuntime: initialize map on UI runtime
  UIRuntime-->>EventHandler: return shareable map
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: replacing makeMutable with createShareable.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

Pull request overview

This PR updates the Reanimated integration in the v3 gesture-handler codepath to use react-native-worklets shareables (createShareable) instead of Reanimated’s makeMutable, and exposes Worklets alongside Reanimated from the shared wrapper.

Changes:

  • Switch the per-handler “last update event” map from Reanimated.makeMutable to Worklets.createShareable.
  • Extend the Reanimated wrapper to optionally load/export react-native-worklets and pass its UI runtime holder during runtime-bindings installation.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts Replaces makeMutable map creation with Worklets shareable map and updates related types/comments.
packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts Adds optional Worklets loading/export and adjusts UI runtime bindings installation setup.
Suppressed comments (1)

packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts:112

  • This comment is inaccurate with the current logic: updateEventMap becomes undefined when Worklets is absent, but Reanimated?.useEvent(...) can still register the callback if Reanimated is present. Either adjust the comment, or (preferably) restore the Reanimated fallback so updateEventMap is only undefined when neither runtime provider is available.
    // Undefined only when Worklets is absent — and then this callback is
    // never registered (`Reanimated?.useEvent` below short-circuits).
    if (updateEventMap === undefined) {

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts`:
- Around line 35-45: The Jest expectation for installUIRuntimeBindings is
failing because runtime binding initialization is invoked more than once. Update
the WorkletsPackage integration in reanimatedWrapper.ts (around lines 35-45) and
the related useReanimatedEventHandler.ts integration (around lines 7-10) so the
binding installation is performed only once, while preserving the existing
shareable-host and event-handler behavior; verify yarn test passes.

In
`@packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts`:
- Around line 31-40: Update the shareable-state fallback in the hook using
Reanimated’s makeMutable API when Worklets.createShareable or UIRuntimeId is
unavailable, so Reanimated.useEvent can still register and execute gesture
callbacks. Revise the comment near the Reanimated.useEvent registration to
reflect that missing Worklets APIs do not short-circuit registration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 386e69e8-e8d3-4d28-8b66-673390aca58e

📥 Commits

Reviewing files that changed from the base of the PR and between 2b37b3c and 6de6580.

📒 Files selected for processing (2)
  • packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts
  • packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts

@tjzel tjzel changed the title @tjzel/drop in worklets refactor: replace makeMutable with createShareable Aug 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/react-native-gesture-handler/src/handlers/gestures/installUIRuntimeBindings.ts`:
- Around line 5-9: Guard the Worklets dependency before invoking
installUIRuntimeBindings: pass Worklets?.getUIRuntimeHolder or initialize the
unsupported-setup contract as an empty object so the installer receives
undefined instead of dereferencing an unavailable module. Add a test covering
the missing react-native-worklets case and confirming graceful installation
without throwing.

In
`@packages/react-native-gesture-handler/src/v3/hooks/callbacks/lastUpdateEventMap.ts`:
- Around line 9-14: Update the capability guard in the last-update event map
factory so missing Worklets.createShareable or Worklets.UIRuntimeId does not
immediately return undefined when Reanimated is available; preserve and use the
existing Reanimated-backed shared-map fallback for those cases, while retaining
the undefined result only when neither supported implementation is available.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92f6f036-b303-4287-a016-f228bf9dae5a

📥 Commits

Reviewing files that changed from the base of the PR and between eb4f501 and 8ac28ff.

📒 Files selected for processing (6)
  • packages/react-native-gesture-handler/src/handlers/gestures/installUIRuntimeBindings.ts
  • packages/react-native-gesture-handler/src/handlers/gestures/installUIRuntimeBindings.web.ts
  • packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts
  • packages/react-native-gesture-handler/src/v3/hooks/callbacks/lastUpdateEventMap.ts
  • packages/react-native-gesture-handler/src/v3/hooks/callbacks/lastUpdateEventMap.web.ts
  • packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts
  • packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts

Comment on lines +5 to +9
export function installUIRuntimeBindings(
getUIRuntimeHolder: (() => object) | undefined
) {
ghQueueMicrotask(() => {
globalThis.__RNGH_UI_WORKLET_RUNTIME_HOLDER = getUIRuntimeHolder?.();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
rg -n -C 5 \
  'installUIRuntimeBindings|Worklets.*getUIRuntimeHolder|require.*react-native-worklets' \
  packages/react-native-gesture-handler/src

Repository: software-mansion/react-native-gesture-handler

Length of output: 2133


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "pwd:"
pwd

echo
echo "repo top-level files:"
git ls-files | head -50

echo
echo "candidate files:"
fd -a 'installUIRuntimeBindings.ts|reanimatedWrapper.ts' . || true

echo
echo "search relevant symbols (if repository available):"
rg -n -C 4 \
  'installUIRuntimeBindings|getUIRuntimeHolder|Worklets|react-native-worklets|__RNGH_UI_WORKLET_RUNTIME_HOLDER' \
  packages/react-native-gesture-handler/src || true

Repository: software-mansion/react-native-gesture-handler

Length of output: 2104


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "pwd:"
pwd

echo
echo "top-level files:"
git ls-files | head -80 || true

echo
echo "candidate files:"
fd -a 'installUIRuntimeBindings.ts|reanimatedWrapper.ts' . || true

echo
echo "package files:"
fd -a 'package.json' packages/react-native-gesture-handler . | head -80 || true

echo
echo "search relevant symbols:"
rg -n -C 4 \
  'installUIRuntimeBindings|getUIRuntimeHolder|Worklets|react-native-worklets|__RNGH_UI_WORKLET_RUNTIME_HOLDER' \
  packages/react-native-gesture-handler/src || true

Repository: software-mansion/react-native-gesture-handler

Length of output: 2104


Guard Worklets.getUIRuntimeHolder before calling installUIRuntimeBindings.

installUIRuntimeBindings accepts undefined, but passing Worklets.getUIRuntimeHolder directly throws when react-native-worklets is unavailable before the installer can degrade gracefully. Pass Worklets?.getUIRuntimeHolder or initialize the Worklets contract as an empty object for unsupported Reanimated setups, and add a missing-module test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react-native-gesture-handler/src/handlers/gestures/installUIRuntimeBindings.ts`
around lines 5 - 9, Guard the Worklets dependency before invoking
installUIRuntimeBindings: pass Worklets?.getUIRuntimeHolder or initialize the
unsupported-setup contract as an empty object so the installer receives
undefined instead of dereferencing an unavailable module. Add a test covering
the missing react-native-worklets case and confirming graceful installation
without throwing.

Source: Coding guidelines

Comment on lines +9 to +14
if (
Worklets?.createShareable === undefined ||
Worklets.UIRuntimeId === undefined ||
Reanimated === undefined
) {
return undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
rg -n -C 10 \
  'createLastUpdateEventMap|lastUpdateEventMap|makeMutable|updateEventMap === undefined|eventHandler\(' \
  packages/react-native-gesture-handler/src/v3

Repository: software-mansion/react-native-gesture-handler

Length of output: 2133


🌐 Web query:

react-native-gesture-handler src v3 hooks callbacks lastUpdateEventMap.ts createLastUpdateEventMap

💡 Result:

In React Native Gesture Handler (RNGH) version 3, the concept of tracking the "last update event" was refactored to optimize performance and reduce dependency on Reanimated context [1]. Historically, in earlier versions of the library, a mutable object or map (often referred to as lastUpdateEvent) was used to store the most recent gesture event by handler tag [2]. This allowed the system to calculate changes between consecutive update events (for callbacks like onChange) [2]. In the transition to RNGH 3 and recent internal updates, the library moved toward a more efficient, shared mutable cache system [1]. Specifically, pull request #4143 replaced per-handler context usage with a shared mutable map in the UI runtime [1]. This architectural change is designed to store the lastUpdateEvent state keyed by handlerTag, which helps reduce the overhead of using gestures with React Native Reanimated [1]. Regarding the specific identifier createLastUpdateEventMap: While specific internal helper functions or files like lastUpdateEventMap.ts may exist within the library's internal source structure as part of this shared cache implementation [1], it is not a public-facing API. Developers are intended to use the high-level hook API (e.g., usePanGesture, useTapGesture) and standard callback props (onUpdate, onFinalize) [3][4], rather than interacting with internal event tracking maps. If you are encountering this in a stack trace or source exploration, it is part of the library's internal state management system for Reanimated integration [1], which automatically handles the synchronization of event data [3].

Citations:


🌐 Web query:

software-mansion react-native-gesture-handler lastUpdateEventMap useReanimatedEventHandler

💡 Result:

In the React Native Gesture Handler library, the useReanimatedEventHandler hook and its associated event handling mechanism recently underwent a significant architectural change to improve performance and reduce Reanimated overhead [1][2]. Specifically, the library moved away from storing the last received event in a per-handler context via useHandler [1][2]. Instead, it now utilizes a shared, global mutable object—a map in the UI runtime—that stores the last event data indexed by handlerTag [1]. This change, implemented in pull request #4143, accomplishes the following: - Eliminates the reliance on per-handler Reanimated context to store lastUpdateEvent data [1][2]. - Introduced a module-level mutable map within useReanimatedEventHandler to track event state across handlers [1]. - Allows event handler update paths to tolerate missing context, preventing errors that previously occurred when context was absent [1]. These optimizations are designed to lower the performance overhead of gesture updates when integrated with Reanimated, specifically for handlers defined using the hook-based API [1]. Developers generally interact with these systems through standard Gesture Handler hooks (e.g., usePanGesture, useTapGesture) and do not need to manually manage the lastUpdateEventMap, as this is an internal mechanism for managing gesture state updates efficiently on the UI thread [1][3][4].

Citations:


🌐 Web query:

raw.githubusercontent.com refs/heads/main packages/react-native-gesture-handler/src/v3/hooks/callbacks/lastUpdateEventMap.ts

💡 Result:

The file path you mentioned, packages/react-native-gesture-handler/src/v3/hooks/callbacks/lastUpdateEventMap.ts, is a component of the React Native Gesture Handler (RNGH) library, specifically introduced in the version 3 (v3) hook-based API architecture [1]. In recent versions of RNGH, specifically as part of efforts to improve performance and reduce Reanimated overhead, the library moved away from relying on per-handler context provided by useHandler [1]. Instead, it introduced a global mutable object (often implemented as a module-level map) that tracks the last received gesture event for each handler, keyed by its handlerTag [1]. Key points regarding this implementation include: - Purpose: By using a shared mutable map in the UI runtime, the library avoids the performance cost of repeatedly accessing and updating React context within the gesture update path [1]. - Functionality: This map (effectively lastUpdateEventMap.ts) acts as a cache that allows the gesture system to retrieve the most recent event data necessary for calculations (such as in changeEventCalculator or internal state management) without context-related overhead [1]. - Context: This shift was part of a larger initiative to optimize the library's performance, particularly when used alongside React Native Reanimated [1]. It allows the library's update/finalize logic to be more resilient, as it no longer throws errors if expected context is missing, relying instead on the global cache [1]. If you are examining this file in the main branch, you are looking at the current implementation that handles event state management globally rather than locally per hook [1].

Citations:


🌐 Web query:

raw.githubusercontent.com refs/heads/main packages/react-native-gesture-handler/src/v3/hooks/callbacks/useReanimatedEventHandler.ts

💡 Result:

The file useReanimatedEventHandler.ts, located in packages/react-native-gesture-handler/src/v3/hooks/callbacks/, is an internal utility within the react-native-gesture-handler library, specifically associated with the version 3 (v3) API [1][2]. Its primary purpose is to facilitate the integration between gesture handlers and React Native Reanimated, enabling gesture callbacks to execute synchronously on the UI thread [3][4]. Key characteristics and recent changes to this file include: Integration Mechanism: It acts as a bridge for processing gesture events using Reanimated's event handling capabilities [3][4]. By leveraging worklets, it allows for high-performance, native-driven touch interactions that avoid the overhead of the JavaScript-to-UI bridge [5][4]. Optimization and Performance: Recent developments, such as the implementation of a shared mutable cache keyed by handlerTag, have been introduced to replace per-hook Reanimated context usage [3]. This change aims to reduce the overhead associated with Reanimated during gesture updates by using a module-level mutable map to store event states [3]. Architectural Role: It is part of the v3 hook-based API, which provides a declarative approach to managing gestures [5]. It ensures that when a gesture callback is provided as a worklet, the library correctly utilizes Reanimated's tools to process the gesture on the UI thread, unless specifically disabled by the user [4]. This file is internal to the library and is not intended for direct use by consumers of react-native-gesture-handler. Developers should instead use the standard gesture hooks (e.g., usePanGesture, useTapGesture) provided by the library, which internally handle Reanimated integration [4].

Citations:


🌐 Web query:

site:raw.githubusercontent.com/software-mansion/react-native-gesture-handler createLastUpdateEventMap Worklets? UIRuntimeId

💡 Result:

There is no publicly available documentation or authoritative technical information regarding a function or identifier named createLastUpdateEventMap in the context of React Native Worklets or UIRuntimeId. The provided search results discuss general Worklet behavior in the context of the React Native Gesture Handler library, but do not reference these specific terms. It is possible these are internal, private, or experimental APIs not intended for public use.

Citations:


🌐 Web query:

React Native Reanimated 3 createShareable UIRuntimeId compatibility worklets

💡 Result:

In React Native Reanimated (via the react-native-worklets package), createShareable is used to create Shareable objects—a type of runtime-bound memory that allows shared access across different worklet runtimes [1][2]. When creating a Shareable, you must explicitly specify a host runtime by passing its runtime ID [1][3]. UIRuntimeId is an exported constant representing the unique ID of the UI Worklet Runtime [4][5]. It is primarily used as the hostRuntimeId argument in createShareable to ensure that the Shareable is hosted on the UI thread, which is the standard practice for many Reanimated features [1][6]. Key points regarding compatibility and usage: 1. Host Runtime Requirement: The createShareable function requires a runtime ID to define where the Shareable lives [1][6]. While you can use UIRuntimeId to target the UI runtime, you can also use the runtimeId property from a Worklet Runtime created via createWorkletRuntime for custom worker runtimes [1][3]. 2. UI Runtime Support: Currently, hosting Shareables on the UI Runtime is the primary supported configuration [6]. In development mode (DEV), attempting to use a runtime ID other than UIRuntimeId may trigger an error, as the library currently enforces that only the UI host runtime is fully supported for this primitive [6]. 3. Integration: UIRuntimeId is also used with other worklet-related APIs, such as runOnRuntimeSyncWithId and scheduleOnRuntimeWithId, providing a consistent way to target the UI thread when you do not have direct access to the runtime object [4][7][5]. In summary, UIRuntimeId is the essential identifier for designating the UI runtime as the host for a Shareable object, ensuring compatibility within the Worklets architecture [1][4].

Citations:


Preserve the Reanimated fallback for unsupported Worklets capabilities.

If Worklets?.createShareable or Worklets.UIRuntimeId is unavailable, returning undefined bypasses the Reanimated-backed shared map. This disables gesture callbacks for supported Reanimated setups that do not provide the React Native Worklets surface. Keep the previous Reanimated-backed fallback available when these flags are missing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react-native-gesture-handler/src/v3/hooks/callbacks/lastUpdateEventMap.ts`
around lines 9 - 14, Update the capability guard in the last-update event map
factory so missing Worklets.createShareable or Worklets.UIRuntimeId does not
immediately return undefined when Reanimated is available; preserve and use the
existing Reanimated-backed shared-map fallback for those cases, while retaining
the undefined result only when neither supported implementation is available.

Source: Coding guidelines

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.

3 participants