From b6f3c3b609242939fdb42ca05b83e91530d511fb Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 17 Mar 2026 11:56:57 +0100 Subject: [PATCH 1/4] feat: introduce more granular event listeners --- README.md | 96 ++++++++++++++-------------- example/App.js | 47 +++++++------- example/ios/Podfile.lock | 4 +- src/DateTimePickerAndroid.android.js | 27 ++++++-- src/datetimepicker.android.js | 8 ++- src/datetimepicker.ios.js | 44 ++++++++----- src/datetimepicker.windows.js | 27 ++++---- src/index.d.ts | 22 ++++++- src/types.js | 23 +++++-- 9 files changed, 184 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 68a4a40f..ee8915ae 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,9 @@ See this [issue](https://github.com/react-native-datetimepicker/datetimepicker/i This repository was moved out of the react native community GH organization, in accordance to [this proposal](https://github.com/react-native-community/discussions-and-proposals/issues/176). The module is still published on `npm` under the old namespace (as documented) but will be published under a new namespace at some point, with a major version bump. -![CircleCI Status][circle-ci-status] +[![npm downloads][npm-downloads-badge]][npm-downloads-link] ![Supports Android and iOS][support-badge] ![MIT License][license-badge] -[![Lean Core Badge][lean-core-badge]][lean-core-issue] React Native date & time picker component for iOS, Android and Windows (please note Windows is not actively maintained). @@ -74,28 +73,30 @@ React Native date & time picker component for iOS, Android and Windows (please n - [Expo users notice](#expo-users-notice) - [Getting started](#getting-started) - [Usage](#usage) - - [React Native Support](#react-native-support) - [Localization note](#localization-note) - [Android imperative API](#android-imperative-api) - [Android styling](#android-styling) - [Props / params](#component-props--params-of-the-android-imperative-api) - [`mode` (`optional`)](#mode-optional) - [`display` (`optional`)](#display-optional) - - [`design` (`optional`, `Android only`)](#design-optional) + - [`design` (`optional`, `Android only`)](#design-optional-android-only) - [`initialInputMode` (`optional`, `Android only`)](#initialinputmode-optional-android-only) - [`title` (`optional`, `Android only`)](#title-optional-android-only) - [`fullscreen` (`optional`, `Android only`)](#fullscreen-optional-android-only) - [`startOnYearSelection` (`optional`, `Android only`)](#startOnYearSelection-optional-android-only) - - [`onChange` (`optional`)](#onchange-optional) + - [`onValueChange` (`optional`)](#onvaluechange-optional) + - [`onDismiss` (`optional`)](#ondismiss-optional) + - [`onNeutralButtonPress` (`optional`, `Android only`)](#onneutralbuttonpress-optional-android-only) + - [`onChange` (`optional`, `deprecated`)](#onchange-optional-deprecated) - [`value` (`required`)](#value-required) - [`maximumDate` (`optional`)](#maximumdate-optional) - [`minimumDate` (`optional`)](#minimumdate-optional) - [`timeZoneName` (`optional`, `iOS or Android only`)](#timeZoneName-optional-ios-and-android-only) - [`timeZoneOffsetInMinutes` (`optional`, `iOS or Android only`)](#timezoneoffsetinminutes-optional-ios-and-android-only) - - [`timeZoneOffsetInSeconds` (`optional`, `Windows only`)](#timezoneoffsetinsecond-optional-windows-only) + - [`timeZoneOffsetInSeconds` (`optional`, `Windows only`)](#timezoneoffsetinseconds-optional-windows-only) - [`dayOfWeekFormat` (`optional`, `Windows only`)](#dayOfWeekFormat-optional-windows-only) - [`dateFormat` (`optional`, `Windows only`)](#dateFormat-optional-windows-only) - - [`firstDayOfWeek` (`optional`, `Windows only`)](#firstDayOfWeek-optional-windows-only) + - [`firstDayOfWeek` (`optional`, `Android and Windows only`)](#firstdayofweek-optional-android-and-windows-only) - [`textColor` (`optional`, `iOS only`)](#textColor-optional-ios-only) - [`accentColor` (`optional`, `iOS only`)](#accentColor-optional-ios-only) - [`themeVariant` (`optional`, `iOS only`)](#themevariant-optional-ios-only) @@ -117,15 +118,12 @@ React Native date & time picker component for iOS, Android and Windows (please n ## Requirements -- Only Android API level >=21 (Android 5), iOS >= 11 are supported. -- Tested with Xcode 14.0 and RN 0.72.7. Other configurations are very likely to work as well but have not been tested. - -The module supports the [new React Native architecture](https://reactnative.dev/docs/next/the-new-architecture/why) (Fabric rendering of iOS components, and turbomodules on Android). If you are using the new architecture, you will need to use React Native 0.71.4 or higher. +The module supports the [new React Native architecture](https://reactnative.dev/docs/next/the-new-architecture/why) (Fabric rendering of iOS components, and turbomodules on Android). ## Expo users notice -This module is part of Expo Managed Workflow - [see docs](https://docs.expo.io/versions/latest/sdk/date-time-picker/). However, Expo SDK in the Managed Workflow may not contain the latest version of the module and therefore, the newest features and bugfixes may not be available in Expo Managed Workflow. -If you use the Managed Workflow, use the command `expo install @react-native-community/datetimepicker` (not `yarn` or `npm`) to install this module - Expo will automatically install the latest version compatible with your Expo SDK (which may _not_ be the latest version of the module available). +This module is part of Expo Go - [see docs](https://docs.expo.io/versions/latest/sdk/date-time-picker/). However, Expo Go may not contain the latest version of the module and therefore, the newest features and bugfixes may not be available. +If you use Expo Go, use the command `npx expo install @react-native-community/datetimepicker` (not `yarn` or `npm`) to install this module - Expo will automatically install the latest version compatible with your Expo SDK (which may _not_ be the latest version of the module available). If you're using a [Dev Client](https://docs.expo.dev/development/create-development-builds/), rebuild the Dev Client after installing the dependencies. @@ -145,20 +143,7 @@ yarn add @react-native-community/datetimepicker Autolinking is not yet implemented on Windows, so [manual installation ](/docs/manual-installation.md) is needed. -#### RN >= 0.60 - -If you are using RN >= 0.60, only run `npx pod-install`. Then rebuild your project. - -## React Native Support - -Check the `react-native` version support table below to find the corresponding `datetimepicker` version to meet support requirements. Maintenance is only provided for last 3 stable react-native versions. - -| react-native version | version | -| -------------------- | ------- | -| 0.73.0+ | 7.6.3+ | -| <=0.72.0 | <=7.6.2 | -| 0.70.0+ | 7.0.1+ | -| <0.70.0 | <=7.0.0 | +On iOS, run `npx pod-install` after installing. Then rebuild your project. ## Usage @@ -181,15 +166,10 @@ Read more about the motivation in [Android imperative API](#android-imperative-a export const App = () => { const [date, setDate] = useState(new Date(1598051730000)); - const onChange = (event, selectedDate) => { - const currentDate = selectedDate; - setDate(currentDate); - }; - const showMode = (currentMode) => { DateTimePickerAndroid.open({ value: date, - onChange, + onValueChange: setDate, mode: currentMode, is24Hour: true, }); @@ -221,12 +201,6 @@ export const App = () => { const [mode, setMode] = useState('date'); const [show, setShow] = useState(false); - const onChange = (event, selectedDate) => { - const currentDate = selectedDate; - setShow(false); - setDate(currentDate); - }; - const showMode = (currentMode) => { setShow(true); setMode(currentMode); @@ -251,7 +225,8 @@ export const App = () => { value={date} mode={mode} is24Hour={true} - onChange={onChange} + onValueChange={setDate} + onDismiss={() => setShow(false)} /> )} @@ -358,7 +333,36 @@ List of possible values ``` -#### `onChange` (`optional`) +#### `onValueChange` (`optional`) + +Called when the user selects a date or time. Receives the selected `Date` as its only argument. + +```js + setDate(date)} /> +``` + +#### `onDismiss` (`optional`) + +Called when the picker is dismissed without selecting a value. Receives no arguments. + +```js + setShow(false)} /> +``` + +#### `onNeutralButtonPress` (`optional`, `Android only`) + +Called when the neutral button is pressed. Receives no arguments. See [`neutralButton`](#neutralButton-optional-android-only). + +```js + clearDate()} +/> +``` + +#### `onChange` (`optional`, `deprecated`) + +> **Deprecated:** Use `onValueChange`, `onDismiss`, and `onNeutralButtonPress` instead. If the new specific listeners are provided, they take precedence over `onChange` for their respective event types. Date change handler. @@ -554,7 +558,7 @@ Set the positive button label and text color. #### `neutralButton` (`optional`, `Android only`) Allows displaying neutral button on picker dialog. -Pressing button can be observed in onChange handler as `event.type === 'neutralButtonPressed'` +Pressing the button can be observed via the [`onNeutralButtonPress`](#onneutralbuttonpress-optional-android-only) callback. ```js @@ -643,9 +647,7 @@ Please see [manual-installation.md](/docs/manual-installation.md) This project is tested with BrowserStack. -[circle-ci-badge]: https://img.shields.io/circleci/project/github/react-native-community/datetimepicker/master.svg?style=flat-square -[circle-ci-status]: https://circleci.com/gh/react-native-datetimepicker/datetimepicker.svg?style=svg +[npm-downloads-badge]: https://img.shields.io/npm/dm/@react-native-community/datetimepicker.svg?style=flat-square +[npm-downloads-link]: https://www.npmjs.com/package/@react-native-community/datetimepicker [support-badge]: https://img.shields.io/badge/platforms-android%20%7C%20ios%20%7C%20windows-lightgrey.svg?style=flat-square -[license-badge]: https://img.shields.io/npm/l/@react-native-community/slider.svg?style=flat-square -[lean-core-badge]: https://img.shields.io/badge/Lean%20Core-Extracted-brightgreen.svg?style=flat-square -[lean-core-issue]: https://github.com/facebook/react-native/issues/23313 +[license-badge]: https://img.shields.io/npm/l/@react-native-community/datetimepicker.svg?style=flat-square diff --git a/example/App.js b/example/App.js index b6346e51..a5afca75 100644 --- a/example/App.js +++ b/example/App.js @@ -138,32 +138,31 @@ export const App = () => { setDate(undefined); }; - const onChange = (event, selectedDate) => { + const onValueChange = (selectedDate) => { + setDate(selectedDate); + }; + + const onDismiss = () => { if (Platform.OS === 'android') { setShow(false); } - if (event.type === 'dismissed') { - Alert.alert( - 'picker was dismissed', - undefined, - [ - { - text: 'great', - }, - ], - {cancelable: true}, - ); - return; - } + Alert.alert( + 'picker was dismissed', + undefined, + [ + { + text: 'great', + }, + ], + {cancelable: true}, + ); + }; - if (event.type === 'neutralButtonPressed') { - setDate(new Date(0)); - } else { - setDate(selectedDate); - } + const onNeutralButtonPress = () => { + setDate(new Date(0)); }; - const onTimeChange = (event: any, newTime?: Date) => { + const onTimeChange = (newTime: Date) => { if (Platform.OS === 'windows') { setTime(newTime); } @@ -523,7 +522,9 @@ export const App = () => { is24Hour locale="en-US" display={display} - onChange={onChange} + onValueChange={onValueChange} + onDismiss={onDismiss} + onNeutralButtonPress={onNeutralButtonPress} textColor={textColor || undefined} accentColor={accentColor || undefined} neutralButton={{label: neutralButtonLabel}} @@ -621,7 +622,7 @@ export const App = () => { { mode="time" value={time} style={{width: 300, opacity: 1, height: 30, marginTop: 50}} - onChange={onTimeChange} + onValueChange={onTimeChange} is24Hour={is24Hours} minuteInterval={interval} /> diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index b75436ef..68a3bb83 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1810,7 +1810,7 @@ PODS: - React-Core - React-jsi - ReactTestApp-Resources (1.0.0-dev) - - RNDateTimePicker (8.6.0): + - RNDateTimePicker (9.0.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2176,7 +2176,7 @@ SPEC CHECKSUMS: ReactNativeHost: 147a222a7c577801639023140b694160987738ef ReactTestApp-DevSupport: 0e7676c00b33b0545e72d89ea09f990d737db6d3 ReactTestApp-Resources: 1bd9ff10e4c24f2ad87101a32023721ae923bccf - RNDateTimePicker: 9c0a849bbe1c256f0854fea255734b715f5ea876 + RNDateTimePicker: a8b45651bfa11872964f1439d1cae9a1712dc108 RNLocalize: 390c6e0c4061855a7bd7a91e21dd6a317b45c46c Yoga: 1f66b0bb07f6c5f0199562b772bfb2ac54cad91a diff --git a/src/DateTimePickerAndroid.android.js b/src/DateTimePickerAndroid.android.js index 8ed335b8..aefa856b 100644 --- a/src/DateTimePickerAndroid.android.js +++ b/src/DateTimePickerAndroid.android.js @@ -38,6 +38,9 @@ function open(props: AndroidNativeProps) { timeZoneOffsetInMinutes, timeZoneName, onChange, + onValueChange, + onDismiss, + onNeutralButtonPress, onError, positiveButton, negativeButton, @@ -99,20 +102,32 @@ function open(props: AndroidNativeProps) { case DATE_SET_ACTION: case TIME_SET_ACTION: { const date = new Date(timestamp); - const [event] = createDateTimeSetEvtParams(date, utcOffset); - onChange?.(event, date); + if (onValueChange) { + onValueChange(date); + } else { + const [event] = createDateTimeSetEvtParams(date, utcOffset); + onChange?.(event, date); + } break; } case NEUTRAL_BUTTON_ACTION: { - const [event] = createNeutralEvtParams(originalValue, utcOffset); - onChange?.(event, originalValue); + if (onNeutralButtonPress) { + onNeutralButtonPress(); + } else { + const [event] = createNeutralEvtParams(originalValue, utcOffset); + onChange?.(event, originalValue); + } break; } case DISMISS_ACTION: default: { - const [event] = createDismissEvtParams(originalValue, utcOffset); - onChange?.(event, originalValue); + if (onDismiss) { + onDismiss(); + } else { + const [event] = createDismissEvtParams(originalValue, utcOffset); + onChange?.(event, originalValue); + } break; } } diff --git a/src/datetimepicker.android.js b/src/datetimepicker.android.js index 3bf75f0b..dd445550 100644 --- a/src/datetimepicker.android.js +++ b/src/datetimepicker.android.js @@ -18,10 +18,13 @@ export default function RNDateTimePickerAndroid( display = ANDROID_DISPLAY.default, value, onChange, + onValueChange, is24Hour, minimumDate, maximumDate, minuteInterval, + onDismiss, + onNeutralButtonPress, onError, timeZoneOffsetInMinutes, timeZoneName, @@ -56,6 +59,9 @@ export default function RNDateTimePickerAndroid( minuteInterval, timeZoneOffsetInMinutes, timeZoneName, + onValueChange, + onDismiss, + onNeutralButtonPress, onError, onChange, positiveButton, @@ -75,7 +81,7 @@ export default function RNDateTimePickerAndroid( // as an alternative, use the DateTimePickerAndroid whose reason for existence is described in // https://github.com/react-native-datetimepicker/datetimepicker/pull/327#issuecomment-723160992 // eslint-disable-next-line react-hooks/exhaustive-deps - [onChange, valueTimestamp, mode], + [onChange, onValueChange, onDismiss, onNeutralButtonPress, valueTimestamp, mode], ); return null; diff --git a/src/datetimepicker.ios.js b/src/datetimepicker.ios.js index d8819f76..846ea4bd 100644 --- a/src/datetimepicker.ios.js +++ b/src/datetimepicker.ios.js @@ -55,6 +55,8 @@ export default function Picker({ accentColor, themeVariant, onChange, + onValueChange, + onDismiss: onDismissProp, mode = IOS_MODE.date, display: providedDisplay = IOS_DISPLAY.default, // $FlowFixMe[incompatible-type] @@ -67,29 +69,37 @@ export default function Picker({ const _onChange = (event: NativeEventIOS) => { const timestamp = event.nativeEvent.timestamp; - const unifiedEvent: DateTimePickerEvent = { - ...event, - type: EVENT_TYPE_SET, - }; - const date = timestamp !== undefined ? new Date(timestamp) : undefined; - onChange && onChange(unifiedEvent, date); + if (onValueChange) { + if (date) { + onValueChange(date); + } + } else if (onChange) { + const unifiedEvent: DateTimePickerEvent = { + ...event, + type: EVENT_TYPE_SET, + }; + onChange(unifiedEvent, date); + } }; const onDismiss = () => { - // TODO introduce separate onDismissed event listener - onChange && - onChange( - { - type: EVENT_TYPE_DISMISSED, - nativeEvent: { - timestamp: value.getTime(), - utcOffset: 0, // TODO vonovak - the dismiss event should not carry any date information + if (onDismissProp) { + onDismissProp(); + } else { + onChange && + onChange( + { + type: EVENT_TYPE_DISMISSED, + nativeEvent: { + timestamp: value.getTime(), + utcOffset: 0, + }, }, - }, - value, - ); + value, + ); + } }; return ( diff --git a/src/datetimepicker.windows.js b/src/datetimepicker.windows.js index 92b10ec1..0e7a3819 100644 --- a/src/datetimepicker.windows.js +++ b/src/datetimepicker.windows.js @@ -53,18 +53,23 @@ export default function RNDateTimePickerQWE( }; const _onChange = (event: WindowsDatePickerChangeEvent) => { - const {onChange} = props; - const unifiedEvent: DateTimePickerEvent = { - ...event, - nativeEvent: { - ...event.nativeEvent, - timestamp: event.nativeEvent.newDate, - utcOffset: 0, - }, - type: EVENT_TYPE_SET, - }; + const {onChange, onValueChange} = props; + const date = new Date(event.nativeEvent.newDate); - onChange && onChange(unifiedEvent, new Date(event.nativeEvent.newDate)); + if (onValueChange) { + onValueChange(date); + } else if (onChange) { + const unifiedEvent: DateTimePickerEvent = { + ...event, + nativeEvent: { + ...event.nativeEvent, + timestamp: event.nativeEvent.newDate, + utcOffset: 0, + }, + type: EVENT_TYPE_SET, + }; + onChange(unifiedEvent, date); + } }; // $FlowFixMe[recursive-definition] diff --git a/src/index.d.ts b/src/index.d.ts index 451f3e63..d4fd9dc5 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -33,12 +33,28 @@ type BaseOptions = { value: Date; /** - * Date change handler. + * @deprecated Use onValueChange, onDismiss, and onNeutralButtonPress instead. * - * This is called when the user changes the date or time in the UI. - * The first argument is an Event, the second a selected Date. + * Called when the user changes the date/time, dismisses the picker, + * or presses the neutral button. The event type is encoded in event.type. + * If the new specific listeners are provided, they take precedence. */ onChange?: (event: DateTimePickerEvent, date?: Date) => void; + + /** + * Called when the user selects a date or time. + */ + onValueChange?: (date: Date) => void; + + /** + * Called when the picker is dismissed without selecting a value. + */ + onDismiss?: () => void; + + /** + * Called when the neutral button is pressed (Android only). + */ + onNeutralButtonPress?: () => void; }; type DateOptions = BaseOptions & { diff --git a/src/types.js b/src/types.js index 94c9cb22..2987be1d 100644 --- a/src/types.js +++ b/src/types.js @@ -50,13 +50,28 @@ type BaseOptions = {| value: Date, /** - * change handler. + * @deprecated Use onValueChange, onDismiss, and onNeutralButtonPress instead. * - * This is called when the user changes the date or time in the UI. - * Or when they clear / dismiss the dialog. - * The first argument is an Event, the second a selected Date. + * Called when the user changes the date/time, dismisses the picker, + * or presses the neutral button. The event type is encoded in event.type. + * If the new specific listeners are provided, they take precedence. */ onChange?: ?(event: DateTimePickerEvent, date?: Date) => void, + + /** + * Called when the user selects a date or time. + */ + onValueChange?: ?(date: Date) => void, + + /** + * Called when the picker is dismissed without selecting a value. + */ + onDismiss?: ?() => void, + + /** + * Called when the neutral button is pressed (Android only). + */ + onNeutralButtonPress?: ?() => void, |}; type DateOptions = {| From 129388e11366317f90c7ceece13ac6d59e16f3d9 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 17 Mar 2026 12:04:29 +0100 Subject: [PATCH 2/4] chore: reduce usage of deprecated rn-core apis --- .../rndatetimepicker/RNDateTimePickerPackage.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java index 7b9274b9..bfab8277 100644 --- a/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java +++ b/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java @@ -1,6 +1,7 @@ package com.reactcommunity.rndatetimepicker; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.facebook.react.BaseReactPackage; @@ -15,7 +16,7 @@ public class RNDateTimePickerPackage extends BaseReactPackage { @Nullable @Override - public NativeModule getModule(String name, ReactApplicationContext reactContext) { + public NativeModule getModule(String name, @NonNull ReactApplicationContext reactContext) { if (name.equals(DatePickerModule.NAME)) { return new DatePickerModule(reactContext); } else if (name.equals(TimePickerModule.NAME)) { @@ -29,6 +30,7 @@ public NativeModule getModule(String name, ReactApplicationContext reactContext) } } + @NonNull @Override public ReactModuleInfoProvider getReactModuleInfoProvider() { return () -> { @@ -61,7 +63,6 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() { MaterialDatePickerModule.NAME, false, // canOverrideExistingModule false, // needsEagerInit - false, // hasConstants false, // isCxxModule isTurboModule // isTurboModule )); @@ -72,7 +73,6 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() { MaterialTimePickerModule.NAME, false, // canOverrideExistingModule false, // needsEagerInit - false, // hasConstants false, // isCxxModule isTurboModule // isTurboModule )); From bc381b87deca8948ca3809378f10c20f7ef82e1a Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 17 Mar 2026 13:02:25 +0100 Subject: [PATCH 3/4] feat: add deprecation warning for `onChange` prop --- example/App.js | 3 +++ src/DateTimePickerAndroid.android.js | 2 ++ src/datetimepicker.android.js | 9 ++++++++- src/datetimepicker.ios.js | 15 +++++++++++++-- src/datetimepicker.windows.js | 8 +++----- src/utils.js | 10 ++++++++++ 6 files changed, 39 insertions(+), 8 deletions(-) diff --git a/example/App.js b/example/App.js index a5afca75..02bbd3a5 100644 --- a/example/App.js +++ b/example/App.js @@ -139,6 +139,9 @@ export const App = () => { }; const onValueChange = (selectedDate) => { + if (Platform.OS === 'android') { + setShow(false); + } setDate(selectedDate); }; diff --git a/src/DateTimePickerAndroid.android.js b/src/DateTimePickerAndroid.android.js index aefa856b..936f972d 100644 --- a/src/DateTimePickerAndroid.android.js +++ b/src/DateTimePickerAndroid.android.js @@ -25,6 +25,7 @@ import { createNeutralEvtParams, } from './eventCreators'; import {processColor} from 'react-native'; +import {warnIfOnChangeIsUsed} from './utils'; function open(props: AndroidNativeProps) { const { @@ -54,6 +55,7 @@ function open(props: AndroidNativeProps) { startOnYearSelection, } = props; validateAndroidProps(props); + warnIfOnChangeIsUsed(onChange); invariant(originalValue, 'A date or time must be specified as `value` prop.'); const valueTimestamp = originalValue.getTime(); diff --git a/src/datetimepicker.android.js b/src/datetimepicker.android.js index dd445550..8bea7d26 100644 --- a/src/datetimepicker.android.js +++ b/src/datetimepicker.android.js @@ -81,7 +81,14 @@ export default function RNDateTimePickerAndroid( // as an alternative, use the DateTimePickerAndroid whose reason for existence is described in // https://github.com/react-native-datetimepicker/datetimepicker/pull/327#issuecomment-723160992 // eslint-disable-next-line react-hooks/exhaustive-deps - [onChange, onValueChange, onDismiss, onNeutralButtonPress, valueTimestamp, mode], + [ + onChange, + onValueChange, + onDismiss, + onNeutralButtonPress, + valueTimestamp, + mode, + ], ); return null; diff --git a/src/datetimepicker.ios.js b/src/datetimepicker.ios.js index 846ea4bd..b986d223 100644 --- a/src/datetimepicker.ios.js +++ b/src/datetimepicker.ios.js @@ -10,7 +10,11 @@ * @flow strict-local */ import RNDateTimePicker from './picker'; -import {dateToMilliseconds, sharedPropsValidation} from './utils'; +import { + dateToMilliseconds, + sharedPropsValidation, + warnIfOnChangeIsUsed, +} from './utils'; import { IOS_DISPLAY, EVENT_TYPE_SET, @@ -63,7 +67,14 @@ export default function Picker({ disabled = false, ...other }: IOSNativeProps): React.Node { - sharedPropsValidation({value, timeZoneOffsetInMinutes, timeZoneName, minimumDate, maximumDate}); + sharedPropsValidation({ + value, + timeZoneOffsetInMinutes, + timeZoneName, + minimumDate, + maximumDate, + }); + warnIfOnChangeIsUsed(onChange); const display = getDisplaySafe(providedDisplay); diff --git a/src/datetimepicker.windows.js b/src/datetimepicker.windows.js index 0e7a3819..846f449b 100644 --- a/src/datetimepicker.windows.js +++ b/src/datetimepicker.windows.js @@ -6,10 +6,7 @@ */ 'use strict'; -import { - requireNativeComponent, - StyleSheet, -} from 'react-native'; +import {requireNativeComponent, StyleSheet} from 'react-native'; import type { WindowsNativeProps, WindowsDatePickerChangeEvent, @@ -17,7 +14,7 @@ import type { } from './types'; import * as React from 'react'; import {EVENT_TYPE_SET, WINDOWS_MODE} from './constants'; -import {sharedPropsValidation} from './utils'; +import {sharedPropsValidation, warnIfOnChangeIsUsed} from './utils'; const styles = StyleSheet.create({ rnDatePicker: { @@ -38,6 +35,7 @@ export default function RNDateTimePickerQWE( props: WindowsNativeProps, ): React.Node { sharedPropsValidation({value: props?.value}); + warnIfOnChangeIsUsed(props.onChange); const localProps = { accessibilityLabel: props.accessibilityLabel, diff --git a/src/utils.js b/src/utils.js index b7775256..96e4b21c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -67,3 +67,13 @@ export function sharedPropsValidation({ ); } } + +let hasWarnedOnChange = false; +export function warnIfOnChangeIsUsed(onChange: ?Function) { + if (__DEV__ && onChange && !hasWarnedOnChange) { + hasWarnedOnChange = true; + console.warn( + 'DateTimePicker: `onChange` is deprecated. Use `onValueChange`, `onDismiss`, and `onNeutralButtonPress` instead.', + ); + } +} From 079a57fb0dbf79d6923ea74dd5e3fe4845b1f9b6 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 17 Mar 2026 13:48:31 +0100 Subject: [PATCH 4/4] refactor: improve compatibility with the old event shape --- README.md | 8 ++++---- example/App.js | 4 ++-- src/DateTimePickerAndroid.android.js | 2 +- src/datetimepicker.ios.js | 6 ++---- src/datetimepicker.windows.js | 2 +- src/index.d.ts | 9 ++++++++- src/types.js | 11 ++++++++++- 7 files changed, 28 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ee8915ae..6c68ff39 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ export const App = () => { const showMode = (currentMode) => { DateTimePickerAndroid.open({ value: date, - onValueChange: setDate, + onValueChange: (event, selectedDate) => setDate(selectedDate), mode: currentMode, is24Hour: true, }); @@ -225,7 +225,7 @@ export const App = () => { value={date} mode={mode} is24Hour={true} - onValueChange={setDate} + onValueChange={(event, selectedDate) => setDate(selectedDate)} onDismiss={() => setShow(false)} /> )} @@ -335,10 +335,10 @@ List of possible values #### `onValueChange` (`optional`) -Called when the user selects a date or time. Receives the selected `Date` as its only argument. +Called when the user selects a date or time. Receives an `event` with `nativeEvent: { timestamp, utcOffset }` and the selected `Date`. ```js - setDate(date)} /> + setDate(date)} /> ``` #### `onDismiss` (`optional`) diff --git a/example/App.js b/example/App.js index 02bbd3a5..f984c6ed 100644 --- a/example/App.js +++ b/example/App.js @@ -138,7 +138,7 @@ export const App = () => { setDate(undefined); }; - const onValueChange = (selectedDate) => { + const onValueChange = (event, selectedDate) => { if (Platform.OS === 'android') { setShow(false); } @@ -165,7 +165,7 @@ export const App = () => { setDate(new Date(0)); }; - const onTimeChange = (newTime: Date) => { + const onTimeChange = (event: any, newTime?: Date) => { if (Platform.OS === 'windows') { setTime(newTime); } diff --git a/src/DateTimePickerAndroid.android.js b/src/DateTimePickerAndroid.android.js index 936f972d..6f5bb3d2 100644 --- a/src/DateTimePickerAndroid.android.js +++ b/src/DateTimePickerAndroid.android.js @@ -105,7 +105,7 @@ function open(props: AndroidNativeProps) { case TIME_SET_ACTION: { const date = new Date(timestamp); if (onValueChange) { - onValueChange(date); + onValueChange({nativeEvent: {timestamp, utcOffset}}, date); } else { const [event] = createDateTimeSetEvtParams(date, utcOffset); onChange?.(event, date); diff --git a/src/datetimepicker.ios.js b/src/datetimepicker.ios.js index b986d223..2925c161 100644 --- a/src/datetimepicker.ios.js +++ b/src/datetimepicker.ios.js @@ -82,10 +82,8 @@ export default function Picker({ const timestamp = event.nativeEvent.timestamp; const date = timestamp !== undefined ? new Date(timestamp) : undefined; - if (onValueChange) { - if (date) { - onValueChange(date); - } + if (onValueChange && date) { + onValueChange(event, date); } else if (onChange) { const unifiedEvent: DateTimePickerEvent = { ...event, diff --git a/src/datetimepicker.windows.js b/src/datetimepicker.windows.js index 846f449b..ab14c93d 100644 --- a/src/datetimepicker.windows.js +++ b/src/datetimepicker.windows.js @@ -55,7 +55,7 @@ export default function RNDateTimePickerQWE( const date = new Date(event.nativeEvent.newDate); if (onValueChange) { - onValueChange(date); + onValueChange({nativeEvent: {timestamp: event.nativeEvent.newDate, utcOffset: 0}}, date); } else if (onChange) { const unifiedEvent: DateTimePickerEvent = { ...event, diff --git a/src/index.d.ts b/src/index.d.ts index d4fd9dc5..423a7fab 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -26,6 +26,13 @@ export type DateTimePickerEvent = { }; }; +export type DateTimePickerChangeEvent = { + nativeEvent: { + timestamp: number; + utcOffset: number; + }; +}; + type BaseOptions = { /** * The currently selected date. @@ -44,7 +51,7 @@ type BaseOptions = { /** * Called when the user selects a date or time. */ - onValueChange?: (date: Date) => void; + onValueChange?: (event: DateTimePickerChangeEvent, date: Date) => void; /** * Called when the picker is dismissed without selecting a value. diff --git a/src/types.js b/src/types.js index 2987be1d..81847c04 100644 --- a/src/types.js +++ b/src/types.js @@ -43,6 +43,15 @@ export type DateTimePickerEvent = { ... }; +export type DateTimePickerChangeEvent = { + nativeEvent: $ReadOnly<{ + timestamp: number, + utcOffset: number, + ... + }>, + ... +}; + type BaseOptions = {| /** * The currently selected date. @@ -61,7 +70,7 @@ type BaseOptions = {| /** * Called when the user selects a date or time. */ - onValueChange?: ?(date: Date) => void, + onValueChange?: ?(event: DateTimePickerChangeEvent, date: Date) => void, /** * Called when the picker is dismissed without selecting a value.