1
1
import moment from 'moment' ;
2
- import React , { Component } from 'react' ;
2
+ import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
3
3
import { StyleProp , StyleSheet , ViewStyle } from 'react-native' ;
4
4
import { DateTimePickerPackage as RNDateTimePicker } from '../../optionalDependencies' ;
5
+ import { useDidUpdate } from 'hooks' ;
5
6
import { Colors } from '../../style' ;
6
7
import Assets from '../../assets' ;
7
8
import { Constants , asBaseComponent , BaseComponentInjectedProps } from '../../commons/new' ;
@@ -108,79 +109,67 @@ export interface DateTimePickerProps {
108
109
testID ?: string ;
109
110
}
110
111
111
- interface DateTimePickerState {
112
- prevValue ?: Date ;
113
- value ?: Date ;
114
- }
115
-
116
112
type DateTimePickerPropsInternal = DateTimePickerProps & BaseComponentInjectedProps ;
117
113
118
- class DateTimePicker extends Component < DateTimePickerPropsInternal , DateTimePickerState > {
119
- static displayName = 'DateTimePicker' ;
120
-
121
- static defaultProps = {
122
- ...TextField . defaultProps ,
123
- mode : MODES . DATE
124
- } ;
125
-
126
- chosenDate ?: Date ;
127
- expandable = React . createRef < ExpandableOverlayMethods > ( ) ;
114
+ function DateTimePicker ( props : DateTimePickerPropsInternal ) {
115
+ const {
116
+ value : propsValue ,
117
+ renderInput,
118
+ editable,
119
+ mode = MODES . DATE ,
120
+ dateFormat,
121
+ timeFormat,
122
+ dateFormatter,
123
+ timeFormatter,
124
+ minimumDate,
125
+ maximumDate,
126
+ locale,
127
+ is24Hour,
128
+ minuteInterval,
129
+ timeZoneOffsetInMinutes,
130
+ themeVariant,
131
+ onChange,
132
+ dialogProps,
133
+ headerStyle,
134
+ // @ts -expect-error
135
+ useCustomTheme,
136
+ testID,
137
+ ...others
138
+ } = props ;
128
139
129
- constructor ( props : DateTimePickerPropsInternal ) {
130
- super ( props ) ;
131
- this . chosenDate = props . value ;
140
+ const [ value , setValue ] = useState ( propsValue ) ;
141
+ const chosenDate = useRef ( propsValue ) ;
142
+ const expandable = useRef < ExpandableOverlayMethods > ( ) ;
132
143
144
+ useEffect ( ( ) => {
133
145
if ( ! RNDateTimePicker ) {
134
146
console . error ( `RNUILib DateTimePicker component requires installing "@react-native-community/datetimepicker" dependency` ) ;
135
147
}
136
- }
137
-
138
- state = {
139
- prevValue : this . props . value ,
140
- value : this . props . value
141
- } ;
148
+ } , [ ] ) ;
142
149
143
- static getDerivedStateFromProps ( nextProps : DateTimePickerProps , prevState : DateTimePickerState ) {
144
- if ( nextProps . value !== prevState . prevValue ) {
145
- return {
146
- prevValue : prevState . value ,
147
- value : nextProps . value
148
- } ;
149
- }
150
- return null ;
151
- }
152
-
153
- handleChange = ( event : any = { } , date : Date ) => {
154
- // NOTE: will be called on Android even when there was no actual change
155
- if ( event . type !== 'dismissed' && date !== undefined ) {
156
- this . chosenDate = date ;
157
-
158
- if ( Constants . isAndroid ) {
159
- this . onDonePressed ( ) ;
160
- }
161
- } else if ( event . type === 'dismissed' && Constants . isAndroid ) {
162
- this . toggleExpandableOverlay ( ) ;
163
- }
164
- } ;
165
-
166
- toggleExpandableOverlay = ( ) => {
167
- this . expandable . current ?. toggleExpandable ?.( ) ;
168
- } ;
150
+ useDidUpdate ( ( ) => {
151
+ setValue ( propsValue ) ;
152
+ } , [ propsValue ] ) ;
169
153
170
- onDonePressed = ( ) => {
171
- this . toggleExpandableOverlay ( ) ;
172
- if ( Constants . isIOS && ! this . chosenDate ) {
173
- // since handleChange() is not called on iOS when there is no actual change
174
- this . chosenDate = new Date ( ) ;
175
- }
176
-
177
- this . props . onChange ?.( this . chosenDate ! ) ;
178
- this . setState ( { value : this . chosenDate } ) ;
179
- } ;
154
+ const _dialogProps = useMemo ( ( ) => {
155
+ return {
156
+ width : '100%' ,
157
+ height : null ,
158
+ bottom : true ,
159
+ centerH : true ,
160
+ containerStyle : styles . dialog ,
161
+ testID : `${ testID } .dialog` ,
162
+ supportedOrientations : [
163
+ 'portrait' ,
164
+ 'landscape' ,
165
+ 'landscape-left' ,
166
+ 'landscape-right'
167
+ ] as DialogProps [ 'supportedOrientations' ] ,
168
+ ...dialogProps
169
+ } ;
170
+ } , [ dialogProps , testID ] ) ;
180
171
181
- getStringValue = ( ) => {
182
- const { value} = this . state ;
183
- const { mode, dateFormat, timeFormat, dateFormatter, timeFormatter} = this . props ;
172
+ const getStringValue = ( ) => {
184
173
if ( value ) {
185
174
switch ( mode ) {
186
175
case MODES . DATE :
@@ -199,72 +188,59 @@ class DateTimePicker extends Component<DateTimePickerPropsInternal, DateTimePick
199
188
}
200
189
} ;
201
190
202
- getDialogProps = ( ) => {
203
- const { testID, dialogProps} = this . props ;
204
- return {
205
- width : '100%' ,
206
- height : null ,
207
- bottom : true ,
208
- centerH : true ,
209
- // onDismiss: this.toggleExpandableOverlay,
210
- containerStyle : styles . dialog ,
211
- testID : `${ testID } .dialog` ,
212
- supportedOrientations : [
213
- 'portrait' ,
214
- 'landscape' ,
215
- 'landscape-left' ,
216
- 'landscape-right'
217
- ] as DialogProps [ 'supportedOrientations' ] ,
218
- ...dialogProps
219
- } ;
220
- } ;
191
+ const toggleExpandableOverlay = useCallback ( ( ) => {
192
+ expandable . current ?. toggleExpandable ?.( ) ;
193
+ } , [ ] ) ;
221
194
222
- renderIOSExpandableOverlay = ( ) => {
223
- return (
224
- < >
225
- { this . renderHeader ( ) }
226
- { this . renderDateTimePicker ( ) }
227
- </ >
228
- ) ;
229
- } ;
195
+ const onDonePressed = useCallback ( ( ) => {
196
+ toggleExpandableOverlay ( ) ;
197
+ if ( Constants . isIOS && ! chosenDate . current ) {
198
+ // since handleChange() is not called on iOS when there is no actual change
199
+ chosenDate . current = new Date ( ) ;
200
+ }
230
201
231
- renderHeader ( ) {
232
- // @ts -expect-error
233
- const { headerStyle , useCustomTheme } = this . props ;
202
+ onChange ?. ( chosenDate . current ! ) ;
203
+ setValue ( chosenDate . current ) ;
204
+ } , [ toggleExpandableOverlay , onChange ] ) ;
234
205
206
+ const handleChange = useCallback ( ( event : any = { } , date : Date ) => {
207
+ // NOTE: will be called on Android even when there was no actual change
208
+ if ( event . type !== 'dismissed' && date !== undefined ) {
209
+ chosenDate . current = date ;
210
+
211
+ if ( Constants . isAndroid ) {
212
+ onDonePressed ( ) ;
213
+ }
214
+ } else if ( event . type === 'dismissed' && Constants . isAndroid ) {
215
+ toggleExpandableOverlay ( ) ;
216
+ }
217
+ } ,
218
+ [ onDonePressed , toggleExpandableOverlay ] ) ;
219
+
220
+ const renderHeader = ( ) => {
235
221
return (
236
222
< View row spread bg-$backgroundDefault paddingH-20 style = { [ styles . header , headerStyle ] } >
237
223
< Button
238
224
link
239
225
iconSource = { Assets . icons . x }
240
226
iconStyle = { { tintColor : Colors . $iconDefault } }
241
- onPress = { this . toggleExpandableOverlay }
227
+ onPress = { toggleExpandableOverlay }
242
228
/>
243
- < Button link iconSource = { Assets . icons . check } useCustomTheme = { useCustomTheme } onPress = { this . onDonePressed } />
229
+ < Button link iconSource = { Assets . icons . check } useCustomTheme = { useCustomTheme } onPress = { onDonePressed } />
244
230
</ View >
245
231
) ;
246
- }
247
-
248
- renderAndroidDateTimePicker = ( { visible} : RenderCustomOverlayProps ) => {
249
- if ( visible ) {
250
- return this . renderDateTimePicker ( ) ;
251
- }
252
232
} ;
253
233
254
- renderDateTimePicker ( ) {
234
+ const renderDateTimePicker = useCallback ( ( ) => {
255
235
if ( ! RNDateTimePicker ) {
256
236
return null ;
257
237
}
258
238
259
- const { value} = this . state ;
260
- const { mode, minimumDate, maximumDate, locale, is24Hour, minuteInterval, timeZoneOffsetInMinutes, themeVariant} =
261
- this . props ;
262
-
263
239
return (
264
240
< RNDateTimePicker
265
241
mode = { mode }
266
242
value = { value || new Date ( ) }
267
- onChange = { this . handleChange }
243
+ onChange = { handleChange }
268
244
minimumDate = { minimumDate }
269
245
maximumDate = { maximumDate }
270
246
locale = { locale }
@@ -275,40 +251,66 @@ class DateTimePicker extends Component<DateTimePickerPropsInternal, DateTimePick
275
251
themeVariant = { themeVariant }
276
252
/>
277
253
) ;
278
- }
279
-
280
- render ( ) {
281
- // @ts -expect-error
282
- const textInputProps = TextField . extractOwnProps ( this . props ) ;
283
- const { renderInput, editable} = this . props ;
254
+ } , [
255
+ mode ,
256
+ value ,
257
+ handleChange ,
258
+ minimumDate ,
259
+ maximumDate ,
260
+ locale ,
261
+ is24Hour ,
262
+ minuteInterval ,
263
+ timeZoneOffsetInMinutes ,
264
+ themeVariant
265
+ ] ) ;
284
266
267
+ const renderIOSExpandableOverlay = ( ) => {
285
268
return (
286
269
< >
287
- < ExpandableOverlay
288
- ref = { this . expandable }
289
- expandableContent = { Constants . isIOS ? this . renderIOSExpandableOverlay ( ) : undefined }
290
- useDialog
291
- dialogProps = { this . getDialogProps ( ) }
292
- disabled = { editable === false }
293
- // NOTE: Android picker comes with its own overlay built-in therefor we're not using ExpandableOverlay for it
294
- renderCustomOverlay = { Constants . isAndroid ? this . renderAndroidDateTimePicker : undefined }
295
- >
296
- { renderInput ? (
297
- renderInput ( { ...this . props , value : this . getStringValue ( ) } )
298
- ) : (
299
- /* @ts -expect-error */
300
- < TextField
301
- { ...textInputProps }
302
- expandable = { ! ! textInputProps . renderExpandableInput }
303
- value = { this . getStringValue ( ) }
304
- />
305
- ) }
306
- </ ExpandableOverlay >
270
+ { renderHeader ( ) }
271
+ { renderDateTimePicker ( ) }
307
272
</ >
308
273
) ;
309
- }
274
+ } ;
275
+
276
+ const renderAndroidDateTimePicker = useCallback ( ( { visible} : RenderCustomOverlayProps ) => {
277
+ if ( visible ) {
278
+ return renderDateTimePicker ( ) ;
279
+ }
280
+ } ,
281
+ [ renderDateTimePicker ] ) ;
282
+
283
+ return (
284
+ < >
285
+ < ExpandableOverlay
286
+ // @ts -expect-error
287
+ ref = { expandable }
288
+ expandableContent = { Constants . isIOS ? renderIOSExpandableOverlay ( ) : undefined }
289
+ useDialog
290
+ dialogProps = { _dialogProps }
291
+ disabled = { editable === false }
292
+ // NOTE: Android picker comes with its own overlay built-in therefor we're not using ExpandableOverlay for it
293
+ renderCustomOverlay = { Constants . isAndroid ? renderAndroidDateTimePicker : undefined }
294
+ >
295
+ { renderInput ? (
296
+ renderInput ( { ...props , value : getStringValue ( ) } )
297
+ ) : (
298
+ /* @ts -expect-error */
299
+ < TextField
300
+ { ...others }
301
+ testID = { testID }
302
+ editable = { editable }
303
+ // @ts -expect-error should be remove after completing TextField migration
304
+ expandable = { ! ! others . renderExpandableInput }
305
+ value = { getStringValue ( ) }
306
+ />
307
+ ) }
308
+ </ ExpandableOverlay >
309
+ </ >
310
+ ) ;
310
311
}
311
312
313
+ DateTimePicker . displayName = 'DateTimePicker' ;
312
314
export { DateTimePicker } ; // For tests
313
315
export default asBaseComponent < DateTimePickerProps > ( DateTimePicker ) ;
314
316
0 commit comments