Skip to content

Commit 9337c44

Browse files
idanlevi1claude
andcommitted
fix(new-arch): align TurboModule openPicker signature across iOS, Android, and JS
Under React Native's new architecture, the TurboModule runtime enforces strict argument-count parity between the JS-side spec, the platform native module, and every JS caller. `react-native-date-picker` currently has three sources of truth for `openPicker` and they disagree: | Layer | Args | |-----------------------------------------------|---------------| | iOS native (`ios/RNDatePickerManager.mm`) | 3 (uses cbs) | | Android native newarch (`DatePickerModule`) | 1 | | Codegen spec (`src/fabric/NativeRNDatePicker.ts`) | 1 | | JS caller iOS (`src/modal.js`) | 3 | | JS caller Android (`src/modal.js`) | 1 | So one of the two platforms is always wrong: - With the spec at 1 arg, the iOS caller's 3-arg invocation is over by two (RN currently permits this, but it is undocumented behaviour and fails strict tooling). - If a consumer patches the spec to 3 args to match iOS (a common workaround once they hit `TurboModule method "openPicker" called with 1 arguments (expected argument count: 3)`), the Android JS caller starts crashing on every modal open. This PR makes all five layers agree on 3 args: - `src/fabric/NativeRNDatePicker.ts` — spec now declares `openPicker(props, onConfirm, onCancel)`. - `android/src/newarch/java/.../DatePickerModule.java` — accepts `Callback onConfirm, Callback onCancel` and ignores them; Android continues to deliver confirm/cancel via the existing `RCTDeviceEventEmitter` flow. - `src/modal.js` — Android branch now passes two no-op callbacks so the 3-arg signature is honoured on both platforms. iOS behaviour is unchanged at runtime (its native side already takes 3 args and uses them). Android behaviour is unchanged at runtime (the callbacks are unused; events still flow via the emitter listeners registered in `useModal`). Encountered downstream in a React Native 0.85 + new-arch banking app where opening any modal date picker (transaction filter date range, loan payment day) on Android crashed with the strict arg-count error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1dcf1c1 commit 9337c44

3 files changed

Lines changed: 19 additions & 3 deletions

File tree

android/src/newarch/java/com/henninghall/date_picker/DatePickerModule.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import androidx.annotation.NonNull;
44

5+
import com.facebook.react.bridge.Callback;
56
import com.facebook.react.bridge.ReactApplicationContext;
67
import com.facebook.react.bridge.ReadableMap;
78

@@ -28,7 +29,13 @@ public void removeListeners(double type) {
2829
}
2930

3031
@Override
31-
public void openPicker(ReadableMap props){
32+
public void openPicker(ReadableMap props, Callback onConfirm, Callback onCancel){
33+
// Android delivers confirm/cancel to JS via the RCTDeviceEventEmitter
34+
// (see DatePickerModuleImpl), so the callbacks supplied here are
35+
// unused. They exist only to keep the TurboModule signature
36+
// consistent with the iOS native module, where iOS *does* use the
37+
// callbacks. Without them the spec would have to diverge by
38+
// platform, which the codegen tooling does not allow.
3239
module.openPicker(props);
3340
}
3441

src/fabric/NativeRNDatePicker.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import { Double, UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes'
55
export interface Spec extends TurboModule {
66
readonly getConstants: () => {}
77
closePicker(): void
8-
openPicker(props: UnsafeObject): void
8+
openPicker(
9+
props: UnsafeObject,
10+
onConfirm: (result: UnsafeObject) => void,
11+
onCancel: () => void,
12+
): void
913
removeListeners(type: Double): void
1014
addListener(eventName: string): void
1115
}

src/modal.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,13 @@ export const useModal = ({ props, id }) => {
7676
useEffect(() => {
7777
if (shouldOpenModal(props, previousProps)) {
7878
closing.current = false
79+
// The TurboModule spec for `openPicker` declares 3 args because iOS
80+
// native uses the callbacks directly. Android routes confirm/cancel
81+
// through the `NativeEventEmitter` registered below, so the
82+
// callbacks here are no-ops — they exist only to satisfy the
83+
// strict arg-count check in the new architecture.
7984
const params = Platform.select({
80-
android: [props],
85+
android: [props, () => {}, () => {}],
8186
ios: [props, onConfirm, onCancel],
8287
})
8388
if (!params) throw Error('Unsupported platform')

0 commit comments

Comments
 (0)