Skip to content

Commit ad3f799

Browse files
authored
feat: introduce more granular event listeners (#1040)
* feat: introduce more granular event listeners * chore: reduce usage of deprecated rn-core apis * feat: add deprecation warning for `onChange` prop * refactor: improve compatibility with the old event shape
1 parent 3c29153 commit ad3f799

File tree

11 files changed

+237
-122
lines changed

11 files changed

+237
-122
lines changed

README.md

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,9 @@ See this [issue](https://github.com/react-native-datetimepicker/datetimepicker/i
2828
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).
2929
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.
3030

31-
![CircleCI Status][circle-ci-status]
31+
[![npm downloads][npm-downloads-badge]][npm-downloads-link]
3232
![Supports Android and iOS][support-badge]
3333
![MIT License][license-badge]
34-
[![Lean Core Badge][lean-core-badge]][lean-core-issue]
3534

3635
React Native date & time picker component for iOS, Android and Windows (please note Windows is not actively maintained).
3736

@@ -74,28 +73,30 @@ React Native date & time picker component for iOS, Android and Windows (please n
7473
- [Expo users notice](#expo-users-notice)
7574
- [Getting started](#getting-started)
7675
- [Usage](#usage)
77-
- [React Native Support](#react-native-support)
7876
- [Localization note](#localization-note)
7977
- [Android imperative API](#android-imperative-api)
8078
- [Android styling](#android-styling)
8179
- [Props / params](#component-props--params-of-the-android-imperative-api)
8280
- [`mode` (`optional`)](#mode-optional)
8381
- [`display` (`optional`)](#display-optional)
84-
- [`design` (`optional`, `Android only`)](#design-optional)
82+
- [`design` (`optional`, `Android only`)](#design-optional-android-only)
8583
- [`initialInputMode` (`optional`, `Android only`)](#initialinputmode-optional-android-only)
8684
- [`title` (`optional`, `Android only`)](#title-optional-android-only)
8785
- [`fullscreen` (`optional`, `Android only`)](#fullscreen-optional-android-only)
8886
- [`startOnYearSelection` (`optional`, `Android only`)](#startOnYearSelection-optional-android-only)
89-
- [`onChange` (`optional`)](#onchange-optional)
87+
- [`onValueChange` (`optional`)](#onvaluechange-optional)
88+
- [`onDismiss` (`optional`)](#ondismiss-optional)
89+
- [`onNeutralButtonPress` (`optional`, `Android only`)](#onneutralbuttonpress-optional-android-only)
90+
- [`onChange` (`optional`, `deprecated`)](#onchange-optional-deprecated)
9091
- [`value` (`required`)](#value-required)
9192
- [`maximumDate` (`optional`)](#maximumdate-optional)
9293
- [`minimumDate` (`optional`)](#minimumdate-optional)
9394
- [`timeZoneName` (`optional`, `iOS or Android only`)](#timeZoneName-optional-ios-and-android-only)
9495
- [`timeZoneOffsetInMinutes` (`optional`, `iOS or Android only`)](#timezoneoffsetinminutes-optional-ios-and-android-only)
95-
- [`timeZoneOffsetInSeconds` (`optional`, `Windows only`)](#timezoneoffsetinsecond-optional-windows-only)
96+
- [`timeZoneOffsetInSeconds` (`optional`, `Windows only`)](#timezoneoffsetinseconds-optional-windows-only)
9697
- [`dayOfWeekFormat` (`optional`, `Windows only`)](#dayOfWeekFormat-optional-windows-only)
9798
- [`dateFormat` (`optional`, `Windows only`)](#dateFormat-optional-windows-only)
98-
- [`firstDayOfWeek` (`optional`, `Windows only`)](#firstDayOfWeek-optional-windows-only)
99+
- [`firstDayOfWeek` (`optional`, `Android and Windows only`)](#firstdayofweek-optional-android-and-windows-only)
99100
- [`textColor` (`optional`, `iOS only`)](#textColor-optional-ios-only)
100101
- [`accentColor` (`optional`, `iOS only`)](#accentColor-optional-ios-only)
101102
- [`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
117118

118119
## Requirements
119120

120-
- Only Android API level >=21 (Android 5), iOS >= 11 are supported.
121-
- Tested with Xcode 14.0 and RN 0.72.7. Other configurations are very likely to work as well but have not been tested.
122-
123-
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.
121+
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).
124122

125123
## Expo users notice
126124

127-
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.
128-
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).
125+
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.
126+
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).
129127

130128
If you're using a [Dev Client](https://docs.expo.dev/development/create-development-builds/), rebuild the Dev Client after installing the dependencies.
131129

@@ -145,20 +143,7 @@ yarn add @react-native-community/datetimepicker
145143

146144
Autolinking is not yet implemented on Windows, so [manual installation ](/docs/manual-installation.md) is needed.
147145

148-
#### RN >= 0.60
149-
150-
If you are using RN >= 0.60, only run `npx pod-install`. Then rebuild your project.
151-
152-
## React Native Support
153-
154-
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.
155-
156-
| react-native version | version |
157-
| -------------------- | ------- |
158-
| 0.73.0+ | 7.6.3+ |
159-
| <=0.72.0 | <=7.6.2 |
160-
| 0.70.0+ | 7.0.1+ |
161-
| <0.70.0 | <=7.0.0 |
146+
On iOS, run `npx pod-install` after installing. Then rebuild your project.
162147

163148
## Usage
164149

@@ -181,15 +166,10 @@ Read more about the motivation in [Android imperative API](#android-imperative-a
181166
export const App = () => {
182167
const [date, setDate] = useState(new Date(1598051730000));
183168

184-
const onChange = (event, selectedDate) => {
185-
const currentDate = selectedDate;
186-
setDate(currentDate);
187-
};
188-
189169
const showMode = (currentMode) => {
190170
DateTimePickerAndroid.open({
191171
value: date,
192-
onChange,
172+
onValueChange: (event, selectedDate) => setDate(selectedDate),
193173
mode: currentMode,
194174
is24Hour: true,
195175
});
@@ -221,12 +201,6 @@ export const App = () => {
221201
const [mode, setMode] = useState('date');
222202
const [show, setShow] = useState(false);
223203

224-
const onChange = (event, selectedDate) => {
225-
const currentDate = selectedDate;
226-
setShow(false);
227-
setDate(currentDate);
228-
};
229-
230204
const showMode = (currentMode) => {
231205
setShow(true);
232206
setMode(currentMode);
@@ -251,7 +225,8 @@ export const App = () => {
251225
value={date}
252226
mode={mode}
253227
is24Hour={true}
254-
onChange={onChange}
228+
onValueChange={(event, selectedDate) => setDate(selectedDate)}
229+
onDismiss={() => setShow(false)}
255230
/>
256231
)}
257232
</SafeAreaView>
@@ -358,7 +333,36 @@ List of possible values
358333
<RNDateTimePicker design="material" />
359334
```
360335

361-
#### `onChange` (`optional`)
336+
#### `onValueChange` (`optional`)
337+
338+
Called when the user selects a date or time. Receives an `event` with `nativeEvent: { timestamp, utcOffset }` and the selected `Date`.
339+
340+
```js
341+
<RNDateTimePicker onValueChange={(event, date) => setDate(date)} />
342+
```
343+
344+
#### `onDismiss` (`optional`)
345+
346+
Called when the picker is dismissed without selecting a value. Receives no arguments.
347+
348+
```js
349+
<RNDateTimePicker onDismiss={() => setShow(false)} />
350+
```
351+
352+
#### `onNeutralButtonPress` (`optional`, `Android only`)
353+
354+
Called when the neutral button is pressed. Receives no arguments. See [`neutralButton`](#neutralButton-optional-android-only).
355+
356+
```js
357+
<RNDateTimePicker
358+
neutralButton={{label: 'Clear', textColor: 'grey'}}
359+
onNeutralButtonPress={() => clearDate()}
360+
/>
361+
```
362+
363+
#### `onChange` (`optional`, `deprecated`)
364+
365+
> **Deprecated:** Use `onValueChange`, `onDismiss`, and `onNeutralButtonPress` instead. If the new specific listeners are provided, they take precedence over `onChange` for their respective event types.
362366
363367
Date change handler.
364368

@@ -554,7 +558,7 @@ Set the positive button label and text color.
554558
#### `neutralButton` (`optional`, `Android only`)
555559

556560
Allows displaying neutral button on picker dialog.
557-
Pressing button can be observed in onChange handler as `event.type === 'neutralButtonPressed'`
561+
Pressing the button can be observed via the [`onNeutralButtonPress`](#onneutralbuttonpress-optional-android-only) callback.
558562

559563
```js
560564
<RNDateTimePicker neutralButton={{label: 'Clear', textColor: 'grey'}} />
@@ -643,9 +647,7 @@ Please see [manual-installation.md](/docs/manual-installation.md)
643647

644648
This project is tested with BrowserStack.
645649

646-
[circle-ci-badge]: https://img.shields.io/circleci/project/github/react-native-community/datetimepicker/master.svg?style=flat-square
647-
[circle-ci-status]: https://circleci.com/gh/react-native-datetimepicker/datetimepicker.svg?style=svg
650+
[npm-downloads-badge]: https://img.shields.io/npm/dm/@react-native-community/datetimepicker.svg?style=flat-square
651+
[npm-downloads-link]: https://www.npmjs.com/package/@react-native-community/datetimepicker
648652
[support-badge]: https://img.shields.io/badge/platforms-android%20%7C%20ios%20%7C%20windows-lightgrey.svg?style=flat-square
649-
[license-badge]: https://img.shields.io/npm/l/@react-native-community/slider.svg?style=flat-square
650-
[lean-core-badge]: https://img.shields.io/badge/Lean%20Core-Extracted-brightgreen.svg?style=flat-square
651-
[lean-core-issue]: https://github.com/facebook/react-native/issues/23313
653+
[license-badge]: https://img.shields.io/npm/l/@react-native-community/datetimepicker.svg?style=flat-square

android/src/main/java/com/reactcommunity/rndatetimepicker/RNDateTimePickerPackage.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.reactcommunity.rndatetimepicker;
22

33

4+
import androidx.annotation.NonNull;
45
import androidx.annotation.Nullable;
56

67
import com.facebook.react.BaseReactPackage;
@@ -15,7 +16,7 @@
1516
public class RNDateTimePickerPackage extends BaseReactPackage {
1617
@Nullable
1718
@Override
18-
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
19+
public NativeModule getModule(String name, @NonNull ReactApplicationContext reactContext) {
1920
if (name.equals(DatePickerModule.NAME)) {
2021
return new DatePickerModule(reactContext);
2122
} else if (name.equals(TimePickerModule.NAME)) {
@@ -29,6 +30,7 @@ public NativeModule getModule(String name, ReactApplicationContext reactContext)
2930
}
3031
}
3132

33+
@NonNull
3234
@Override
3335
public ReactModuleInfoProvider getReactModuleInfoProvider() {
3436
return () -> {
@@ -61,7 +63,6 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() {
6163
MaterialDatePickerModule.NAME,
6264
false, // canOverrideExistingModule
6365
false, // needsEagerInit
64-
false, // hasConstants
6566
false, // isCxxModule
6667
isTurboModule // isTurboModule
6768
));
@@ -72,7 +73,6 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() {
7273
MaterialTimePickerModule.NAME,
7374
false, // canOverrideExistingModule
7475
false, // needsEagerInit
75-
false, // hasConstants
7676
false, // isCxxModule
7777
isTurboModule // isTurboModule
7878
));

example/App.js

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -138,29 +138,31 @@ export const App = () => {
138138
setDate(undefined);
139139
};
140140

141-
const onChange = (event, selectedDate) => {
141+
const onValueChange = (event, selectedDate) => {
142142
if (Platform.OS === 'android') {
143143
setShow(false);
144144
}
145-
if (event.type === 'dismissed') {
146-
Alert.alert(
147-
'picker was dismissed',
148-
undefined,
149-
[
150-
{
151-
text: 'great',
152-
},
153-
],
154-
{cancelable: true},
155-
);
156-
return;
157-
}
145+
setDate(selectedDate);
146+
};
158147

159-
if (event.type === 'neutralButtonPressed') {
160-
setDate(new Date(0));
161-
} else {
162-
setDate(selectedDate);
148+
const onDismiss = () => {
149+
if (Platform.OS === 'android') {
150+
setShow(false);
163151
}
152+
Alert.alert(
153+
'picker was dismissed',
154+
undefined,
155+
[
156+
{
157+
text: 'great',
158+
},
159+
],
160+
{cancelable: true},
161+
);
162+
};
163+
164+
const onNeutralButtonPress = () => {
165+
setDate(new Date(0));
164166
};
165167

166168
const onTimeChange = (event: any, newTime?: Date) => {
@@ -523,7 +525,9 @@ export const App = () => {
523525
is24Hour
524526
locale="en-US"
525527
display={display}
526-
onChange={onChange}
528+
onValueChange={onValueChange}
529+
onDismiss={onDismiss}
530+
onNeutralButtonPress={onNeutralButtonPress}
527531
textColor={textColor || undefined}
528532
accentColor={accentColor || undefined}
529533
neutralButton={{label: neutralButtonLabel}}
@@ -621,7 +625,7 @@ export const App = () => {
621625
<DateTimePicker
622626
testID="dateTimePicker"
623627
value={date}
624-
onChange={onChange}
628+
onValueChange={onValueChange}
625629
style={styles.windowsPicker}
626630
firstDayOfWeek={firstDayOfWeek}
627631
maxDate={maxDate}
@@ -671,7 +675,7 @@ export const App = () => {
671675
mode="time"
672676
value={time}
673677
style={{width: 300, opacity: 1, height: 30, marginTop: 50}}
674-
onChange={onTimeChange}
678+
onValueChange={onTimeChange}
675679
is24Hour={is24Hours}
676680
minuteInterval={interval}
677681
/>

example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,7 +1810,7 @@ PODS:
18101810
- React-Core
18111811
- React-jsi
18121812
- ReactTestApp-Resources (1.0.0-dev)
1813-
- RNDateTimePicker (8.6.0):
1813+
- RNDateTimePicker (9.0.0):
18141814
- hermes-engine
18151815
- RCTRequired
18161816
- RCTTypeSafety
@@ -2176,7 +2176,7 @@ SPEC CHECKSUMS:
21762176
ReactNativeHost: 147a222a7c577801639023140b694160987738ef
21772177
ReactTestApp-DevSupport: 0e7676c00b33b0545e72d89ea09f990d737db6d3
21782178
ReactTestApp-Resources: 1bd9ff10e4c24f2ad87101a32023721ae923bccf
2179-
RNDateTimePicker: 9c0a849bbe1c256f0854fea255734b715f5ea876
2179+
RNDateTimePicker: a8b45651bfa11872964f1439d1cae9a1712dc108
21802180
RNLocalize: 390c6e0c4061855a7bd7a91e21dd6a317b45c46c
21812181
Yoga: 1f66b0bb07f6c5f0199562b772bfb2ac54cad91a
21822182

src/DateTimePickerAndroid.android.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
createNeutralEvtParams,
2626
} from './eventCreators';
2727
import {processColor} from 'react-native';
28+
import {warnIfOnChangeIsUsed} from './utils';
2829

2930
function open(props: AndroidNativeProps) {
3031
const {
@@ -38,6 +39,9 @@ function open(props: AndroidNativeProps) {
3839
timeZoneOffsetInMinutes,
3940
timeZoneName,
4041
onChange,
42+
onValueChange,
43+
onDismiss,
44+
onNeutralButtonPress,
4145
onError,
4246
positiveButton,
4347
negativeButton,
@@ -51,6 +55,7 @@ function open(props: AndroidNativeProps) {
5155
startOnYearSelection,
5256
} = props;
5357
validateAndroidProps(props);
58+
warnIfOnChangeIsUsed(onChange);
5459
invariant(originalValue, 'A date or time must be specified as `value` prop.');
5560

5661
const valueTimestamp = originalValue.getTime();
@@ -99,20 +104,32 @@ function open(props: AndroidNativeProps) {
99104
case DATE_SET_ACTION:
100105
case TIME_SET_ACTION: {
101106
const date = new Date(timestamp);
102-
const [event] = createDateTimeSetEvtParams(date, utcOffset);
103-
onChange?.(event, date);
107+
if (onValueChange) {
108+
onValueChange({nativeEvent: {timestamp, utcOffset}}, date);
109+
} else {
110+
const [event] = createDateTimeSetEvtParams(date, utcOffset);
111+
onChange?.(event, date);
112+
}
104113
break;
105114
}
106115

107116
case NEUTRAL_BUTTON_ACTION: {
108-
const [event] = createNeutralEvtParams(originalValue, utcOffset);
109-
onChange?.(event, originalValue);
117+
if (onNeutralButtonPress) {
118+
onNeutralButtonPress();
119+
} else {
120+
const [event] = createNeutralEvtParams(originalValue, utcOffset);
121+
onChange?.(event, originalValue);
122+
}
110123
break;
111124
}
112125
case DISMISS_ACTION:
113126
default: {
114-
const [event] = createDismissEvtParams(originalValue, utcOffset);
115-
onChange?.(event, originalValue);
127+
if (onDismiss) {
128+
onDismiss();
129+
} else {
130+
const [event] = createDismissEvtParams(originalValue, utcOffset);
131+
onChange?.(event, originalValue);
132+
}
116133
break;
117134
}
118135
}

0 commit comments

Comments
 (0)