Skip to main content

Overview

react-native-knotapi 3.0.0 is a native TurboModule: React Native’s codegen generates its interface, and it no longer relies on React Native’s legacy-module interop layer. The JS API you call (Knot, addKnotListener, the knot:* events and the payload types) is unchanged. What changes is the set of versions it builds on, how it is mocked in Jest, and some event behaviour that 2.x got wrong or that differed between iOS and Android.

Requirements

The React Native 0.76 template already sets iOS 15.1 (min_ios_version_supported), minSdkVersion 24 and compileSdkVersion 35, and React Native’s Gradle plugin compiles every Android module for Java 17 from 0.73 on. So an app on 0.76+ that kept the template’s values changes nothing here. Not supported, as in 2.x: Expo Go (the SDK ships native code), Mac Catalyst and visionOS (the bundled KnotAPI.xcframework has only iOS device and simulator slices). Expo apps that use a development build / prebuild are supported. If you can’t meet these yet, see Staying on 2.x.

Upgrade steps

  1. On React Native below 0.76, upgrade React Native first, with the React Native Upgrade Helper. Until then, stay on 2.x.
  2. Install 3.0.0:
  3. iOS.
    • CocoaPods: in ios/Podfile, use platform :ios, min_ios_version_supported (15.1 on React Native 0.76+). If you hard-coded platform :ios, '14.0' for 2.x on React Native 0.73–0.75, put min_ios_version_supported back: on 0.76+ React Native’s own pods need 15.1 too, so pod install fails with “required a higher minimum deployment target” until you do.
    • Set the app target’s Minimum Deployments to iOS 15.1 or later in Xcode (IPHONEOS_DEPLOYMENT_TARGET). This applies to CocoaPods and SwiftPM mode alike.
    • Reinstall the native dependencies. With CocoaPods (the default):
      Coming from 2.2.1, the KnotAPI pod disappears from Podfile.lock. That’s expected: the native Knot iOS SDK has been bundled inside this package since 2.3.0. In React Native’s SwiftPM mode (React Native 0.87+, experimental), run this instead of pod install:
    • Delete Xcode’s DerivedData for the app (Xcode → Settings → Locations, or rm -rf ~/Library/Developer/Xcode/DerivedData/<YourApp>-*), so no build product from the 2.x module is reused.
  4. Android. Build with JDK 17 (JAVA_HOME, or Android Studio’s Gradle JDK setting), then clean once:
    If your Android host doesn’t apply the React Native Gradle plugin, see Android hosts without com.facebook.react.
  5. Rebuild and reinstall the app (npx react-native run-ios / run-android, or your usual build). A Metro reload isn’t enough after a native module changes.
  6. Check your code:

Code changes

Jest mocks

In 3.0.0 the module is looked up through TurboModuleRegistry, which reads React Native’s own NativeModules object, not the react-native export your test mocks. So a 2.x mock that returns a new NativeModules from jest.mock('react-native', …) no longer reaches the module, and importing react-native-knotapi throws the not-linked error below. A hand-written react-native mock that doesn’t spread jest.requireActual('react-native') fails earlier, with TypeError: Cannot read properties of undefined (reading 'get'), because it has no TurboModuleRegistry. A mock that instead sets NativeModules.Knotapi on React Native’s actual module still happens to work, through React Native’s fallback to legacy modules under Jest. Either way, mock the package itself with the mock it ships, in a test file or your Jest setup file:
The mock exports the same names as the package:
  • Knot.open, Knot.openWithoutDelay and Knot.close are jest.fn();
  • Knot.getSdkVersion is a jest.fn() that returns undefined;
  • addKnotListener is a jest.fn() that returns { remove: jest.fn() };
  • the default export has open, close, getSdkVersion (returns null), addListener and removeListeners as jest.fn().
Assert on these, for example expect(Knot.open).toHaveBeenCalledWith(...).

The default export

In 2.x the default export was React Native’s NativeModules.Knotapi, typed any. In 3.0.0 it is the TurboModule itself, typed with the module’s codegen Spec: open(params), close(), getSdkVersion(): string | null, addListener(eventName) and removeListeners(count). Prefer the named exports, Knot and addKnotListener: they are the documented API, and Knot.getSdkVersion() returns undefined where the module returns null. If you use the default export, call its methods directly. Don’t spread it, copy it with Object.assign, or list its methods with Object.keys: on the New Architecture a method only becomes an own property of the module object once it has been read, so those give an incomplete, timing-dependent set.

Unchanged

  • Knot.open, Knot.openWithoutDelay, Knot.close, Knot.getSdkVersion and addKnotListener.
  • The event names (knot:onSuccess, knot:onError, knot:onEvent, knot:onExit) and the exported types (KnotSuccess, KnotError, KnotEvent, KnotExit).
  • The error when the native module isn’t linked. It is still thrown when the package is imported (the pod install line appears on iOS only):
    In React Native’s SwiftPM mode, run npx react-native spm instead of pod install.

Behaviour changes

This list is the changelog of what 3.0.0 changes at runtime, compared with 2.2.1 and 2.3.0.

open() input

Errors and onExit

Event payloads

Not changed in 3.0.0

  • An invalid clientId is still Invalid_Client_Id on iOS and Invalid_Session on Android (the native SDKs differ).
  • Events say product: 'transaction_link', while open() accepts 'transactions_link'.
  • domainUrls is Android only, and iOS ignores product (the SDK takes it from the session).

Reloads and React hosts

If the JS bundle reloads while the Knot UI is open (a dev reload, or an OTA restart such as expo-updates’ reloadAsync), the module closes the Knot UI, and that flow sends nothing more, including knot:onExit, to the reloaded JS. Open a new session from the reloaded app if you need one. One React Native host per process is supported. With more than one, the Knot UI’s events go to the host whose Knot.open last opened it, and Knot.close() from any host closes it. The React Native SDK page has the details.

Android hosts without com.facebook.react

On the New Architecture, an Android host must build React Native’s appmodules library, which only the React Native Gradle plugin (com.facebook.react) builds. This is React Native’s requirement, not one this package adds. Two setups work:
  • the app applies the plugin, as every React Native CLI and Expo app does;
  • a library module applies it and is packaged as an AAR for a native host, for example with Callstack’s React Native Brownfield.
A hand-written Gradle host that applies the plugin nowhere builds, but fails at launch with 'PlatformConstants' could not be found (a React Native core module), before react-native-knotapi is looked up. For an AAR host:
  • the AAR’s metadata names com.facebook.react:hermes-android:<React Native version>, which isn’t published. Substitute com.facebook.hermes:hermes-android at the HERMES_VERSION_NAME in react-native/sdks/hermes-engine/version.properties;
  • the host’s compileSdk must be at least the React Native template’s (37 on React Native 0.87);
  • the native Knot Android SDK (com.knotapi.knot:knotapi-android-sdk) reaches the host as a dependency of the AAR. A host that drops transitive dependencies must add it itself.

Staying on 2.x

If you can’t move to React Native 0.76 or iOS 15.1 yet, stay on 2.x:
Or pin the range in package.json:
From 3.0.0 on, latest points at 3.x and legacy-2x at the newest 2.x release. 2.x needs React Native 0.73 or later and iOS 14.0 or later; see the 2.3.0 README on npm.