From 85341e204ad2d0753ac0fc9e3481dce6cbd1fae7 Mon Sep 17 00:00:00 2001 From: Christopher Pruijsen Date: Thu, 10 Sep 2026 23:06:50 +0100 Subject: [PATCH] fix(types): include optional nativeEvent.target on picker events iOS native view events include the React tag RN injects as nativeEvent.target, but the shared TypeScript type omitted it, so accessing that field was a type error even though the value is present at runtime. --- README.md | 3 ++- src/index.d.ts | 2 ++ src/types.js | 3 +++ test/datetimepickerEvent.types.ts | 39 +++++++++++++++++++++++++++++++ test/types.test.js | 30 ++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 test/datetimepickerEvent.types.ts create mode 100644 test/types.test.js diff --git a/README.md b/README.md index 926ba472..3246ebbe 100644 --- a/README.md +++ b/README.md @@ -335,7 +335,7 @@ List of possible values #### `onValueChange` (`optional`) -Called when the user selects a date or time. Receives an `event` with `nativeEvent: { timestamp, utcOffset }` and the selected `Date`. +Called when the user selects a date or time. Receives an `event` with `nativeEvent: { timestamp, utcOffset }` and the selected `Date`. On iOS, `nativeEvent.target` is also present (the React tag of the picker view). ```js setDate(date)} /> @@ -371,6 +371,7 @@ It is also called when user dismisses the picker, which you can detect by checki The values can be: `'set' | 'dismissed' | 'neutralButtonPressed'`. (`neutralButtonPressed` is only available on Android). The `utcOffset` field is only available on Android and iOS. It is the offset in minutes between the selected date and UTC time. +On iOS, `nativeEvent.target` is the React tag of the picker view (injected by React Native). It is omitted on Android, where the picker is a dialog rather than a view, and on iOS dismiss events. ```js const setDate = (event: DateTimePickerEvent, date: Date) => { diff --git a/src/index.d.ts b/src/index.d.ts index 24d0eb74..894ee711 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -23,6 +23,7 @@ export type DateTimePickerEvent = { nativeEvent: { timestamp: number; utcOffset: number; + target?: number; }; }; @@ -30,6 +31,7 @@ export type DateTimePickerChangeEvent = { nativeEvent: { timestamp: number; utcOffset: number; + target?: number; }; }; diff --git a/src/types.js b/src/types.js index 98af06c3..fc9339b8 100644 --- a/src/types.js +++ b/src/types.js @@ -30,6 +30,7 @@ export type NativeEventIOS = SyntheticEvent< $ReadOnly<{| timestamp: number, utcOffset: number, + target?: number, |}>, >; @@ -38,6 +39,7 @@ export type DateTimePickerEvent = { nativeEvent: $ReadOnly<{ timestamp: number, utcOffset: number, + target?: number, ... }>, ... @@ -47,6 +49,7 @@ export type DateTimePickerChangeEvent = { nativeEvent: $ReadOnly<{ timestamp: number, utcOffset: number, + target?: number, ... }>, ... diff --git a/test/datetimepickerEvent.types.ts b/test/datetimepickerEvent.types.ts new file mode 100644 index 00000000..52dac5ae --- /dev/null +++ b/test/datetimepickerEvent.types.ts @@ -0,0 +1,39 @@ +import type { + DateTimePickerChangeEvent, + DateTimePickerEvent, +} from '../src/index'; + +// Android dialogs and iOS dismiss events omit `target`. +const androidEvent: DateTimePickerEvent = { + type: 'set', + nativeEvent: { + timestamp: 0, + utcOffset: 0, + }, +}; + +// iOS native view events include RN's injected react tag. +const iosEvent: DateTimePickerEvent = { + type: 'set', + nativeEvent: { + timestamp: 0, + utcOffset: 0, + target: 42, + }, +}; + +declare const event: DateTimePickerEvent; +declare const changeEvent: DateTimePickerChangeEvent; + +const target: number | undefined = event.nativeEvent.target; +const changeTarget: number | undefined = changeEvent.nativeEvent.target; + +if (event.nativeEvent.target) { + const reactTag: number = event.nativeEvent.target; + void reactTag; +} + +void androidEvent; +void iosEvent; +void target; +void changeTarget; diff --git a/test/types.test.js b/test/types.test.js new file mode 100644 index 00000000..4450b087 --- /dev/null +++ b/test/types.test.js @@ -0,0 +1,30 @@ +import {spawnSync} from 'child_process'; +import path from 'path'; + +describe('TypeScript declarations', () => { + it('allows optional nativeEvent.target on picker events (issue #927)', () => { + const result = spawnSync( + process.execPath, + [ + require.resolve('typescript/bin/tsc'), + '--noEmit', + '--strict', + '--esModuleInterop', + '--skipLibCheck', + '--jsx', + 'react-native', + '--moduleResolution', + 'node', + '--module', + 'commonjs', + '--target', + 'esnext', + path.join(__dirname, 'datetimepickerEvent.types.ts'), + ], + {encoding: 'utf8', cwd: path.join(__dirname, '..')}, + ); + + const output = `${result.stdout}${result.stderr}`; + expect({status: result.status, output}).toEqual({status: 0, output: ''}); + }); +});