Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using the spread operator on Native modules breaks compatibility layer for bridgeless mode #43221

Closed
gabrieldonadel opened this issue Feb 28, 2024 · 6 comments
Labels
Impact: Regression Describes a behavior that used to work on a prior release, but stopped working recently. Needs: React Native Team Attention p: Expo Partner: Expo Partner Type: New Architecture Issues and PRs related to new architecture (Fabric/Turbo Modules)

Comments

@gabrieldonadel
Copy link
Collaborator

Description

While investigating why the compatibility layer was not working for react-native-netinfo when bridgeless mode was tuned on, I noticed that they were using the spread operator (...) directly on the object returned from React Native's NativeModules e.g.

import {NativeModules} from 'react-native';

const RNCNetInfo: NetInfoNativeModule = NativeModules.RNCNetInfo;

export default {
  ...RNCNetInfo,
  get eventEmitter(): NativeEventEmitter {
     ...
  }
}

This is not a problem when bridgeless mode is tuned off, but as soon as bridgeless mode is tuned on, trying to access any of the module functions will result in an unhandled promise rejection

image

Upon some investigation, I noticed that this same error also happens if we try to use the spread operator over a turbo module (with bridgeless mode on and off). I believe that's the case because the module object is a host object and doesn't quite support the ... syntax yet.

To fix this specific case I just used Object.assign instead (check react-native-netinfo/react-native-netinfo#717 for more details), but thinking from a compatibility perspective we should try to address this so that old/unmaintained libraries that use this pattern can benefit from the compatibility layer

Steps to reproduce

With bridgeless mode ON:

  1. Create a NativeModule
  2. Access the module on the JS side
  3. Export the module using the spread operator

Or just clone https://github.com/gabrieldonadel/rn-spread-operator-bug and run the app, it already includes all the use cases

React Native Version

0.74.0-rc.1

Affected Platforms

Runtime - Android, Runtime - iOS

Output of npx react-native info

System:
  OS: macOS 14.0
  CPU: (12) arm64 Apple M2 Max
  Memory: 91.98 MB / 32.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 18.18.2
    path: ~/.volta/tools/image/node/18.18.2/bin/node
  Yarn:
    version: 3.6.4
    path: ~/.volta/tools/image/yarn/1.22.21/bin/yarn
  npm:
    version: 9.8.1
    path: ~/.volta/tools/image/node/18.18.2/bin/npm
  Watchman: Not Found
Managers:
  CocoaPods:
    version: 1.14.3
    path: /Users/gabriel/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 23.2
      - iOS 17.2
      - macOS 14.2
      - tvOS 17.2
      - visionOS 1.0
      - watchOS 10.2
  Android SDK:
    API Levels:
      - "22"
      - "26"
      - "30"
      - "31"
      - "33"
      - "34"
    Build Tools:
      - 26.0.3
      - 30.0.3
      - 31.0.0
      - 33.0.0
      - 33.0.1
      - 33.0.2
      - 34.0.0
    System Images:
      - android-22 | ARM 64 v8a
      - android-26 | Google APIs Intel x86_64 Atom
      - android-30 | ARM 64 v8a
      - android-33 | Google APIs ARM 64 v8a
      - android-33 | Google Play ARM 64 v8a
      - android-34 | Google Play ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2023.1 AI-231.9392.1.2311.11076708
  Xcode:
    version: 15.2/15C500b
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.8
    path: /usr/bin/javac
  Ruby:
    version: 2.7.8
    path: /Users/gabriel/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.2.0
    wanted: 18.2.0
  react-native:
    installed: 0.74.0-rc.1
    wanted: 0.74.0-rc.1
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

Stacktrace or Logs

TypeError: _index.default.helloWorld is not a function (it is undefined), js engine: hermes

Reproducer

https://github.com/gabrieldonadel/rn-spread-operator-bug

Screenshots and Videos

bridgeless on bridgeless off
Screen.Recording.2024-02-27.at.21.45.57.mov
Screen.Recording.2024-02-27.at.21.44.31.mov
Screen.Recording.2024-02-27.at.21.43.08.mov
Screen.Recording.2024-02-27.at.21.41.17.mov
@cipolleschi
Copy link
Contributor

Hi @gabrieldonadel, thanks for opening the issue.
I added it in our tasks to work on before 0.75. I don't think we would be able to fix it in time for 0.74 stable, and it looks like the problem was there also in 0.73. Is that correct?

@gabrieldonadel
Copy link
Collaborator Author

Hi @gabrieldonadel, thanks for opening the issue. I added it in our tasks to work on before 0.75. I don't think we would be able to fix it in time for 0.74 stable, and it looks like the problem was there also in 0.73. Is that correct?

Yes that's correct, but it was probably only noticed now because bridgeless will be enabled by default

@cortinico cortinico added Impact: Regression Describes a behavior that used to work on a prior release, but stopped working recently. Needs: React Native Team Attention and removed Needs: Triage 🔍 labels Mar 1, 2024
@javache
Copy link
Member

javache commented Mar 19, 2024

The spread operator only copies the object's own enumerable properties, and since we use object prototypes now to lazily load turbomodule methods, this will no longer work.

I suggest you use

Object.create(NativeModules.RNCNetInfo, {
   eventEmitter: {
     get: () => {...},
     enumerable: true,
  },
})

@gabrieldonadel
Copy link
Collaborator Author

I think that's not the only thing that causes this to fail, here is another example where this is a problem invertase/react-native-firebase#7688

@javache
Copy link
Member

javache commented Mar 19, 2024

Object.keys also just returns the object's own keys, which wouldn't contain keys from the prototype. A for .. in loop would cover these.

@cortinico
Copy link
Contributor

The spread operator only copies the object's own enumerable properties, and since we use object prototypes now to lazily load turbomodule methods, this will no longer work.

Closing as per this reason. We won't be able to fix this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Impact: Regression Describes a behavior that used to work on a prior release, but stopped working recently. Needs: React Native Team Attention p: Expo Partner: Expo Partner Type: New Architecture Issues and PRs related to new architecture (Fabric/Turbo Modules)
Projects
None yet
Development

No branches or pull requests

5 participants