11import moment from 'moment' ;
2- import React , { Component } from 'react' ;
2+ import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
33import { StyleProp , StyleSheet , ViewStyle } from 'react-native' ;
44import { DateTimePickerPackage as RNDateTimePicker } from '../../optionalDependencies' ;
5+ import { useDidUpdate } from 'hooks' ;
56import { Colors } from '../../style' ;
67import Assets from '../../assets' ;
78import { Constants , asBaseComponent , BaseComponentInjectedProps } from '../../commons/new' ;
@@ -108,79 +109,67 @@ export interface DateTimePickerProps {
108109 testID ?: string ;
109110}
110111
111- interface DateTimePickerState {
112- prevValue ?: Date ;
113- value ?: Date ;
114- }
115-
116112type DateTimePickerPropsInternal = DateTimePickerProps & BaseComponentInjectedProps ;
117113
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 ;
128139
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 > ( ) ;
132143
144+ useEffect ( ( ) => {
133145 if ( ! RNDateTimePicker ) {
134146 console . error ( `RNUILib DateTimePicker component requires installing "@react-native-community/datetimepicker" dependency` ) ;
135147 }
136- }
137-
138- state = {
139- prevValue : this . props . value ,
140- value : this . props . value
141- } ;
148+ } , [ ] ) ;
142149
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 ] ) ;
169153
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 ] ) ;
180171
181- getStringValue = ( ) => {
182- const { value} = this . state ;
183- const { mode, dateFormat, timeFormat, dateFormatter, timeFormatter} = this . props ;
172+ const getStringValue = ( ) => {
184173 if ( value ) {
185174 switch ( mode ) {
186175 case MODES . DATE :
@@ -199,72 +188,59 @@ class DateTimePicker extends Component<DateTimePickerPropsInternal, DateTimePick
199188 }
200189 } ;
201190
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+ } , [ ] ) ;
221194
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+ }
230201
231- renderHeader ( ) {
232- // @ts -expect-error
233- const { headerStyle , useCustomTheme } = this . props ;
202+ onChange ?. ( chosenDate . current ! ) ;
203+ setValue ( chosenDate . current ) ;
204+ } , [ toggleExpandableOverlay , onChange ] ) ;
234205
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 = ( ) => {
235221 return (
236222 < View row spread bg-$backgroundDefault paddingH-20 style = { [ styles . header , headerStyle ] } >
237223 < Button
238224 link
239225 iconSource = { Assets . icons . x }
240226 iconStyle = { { tintColor : Colors . $iconDefault } }
241- onPress = { this . toggleExpandableOverlay }
227+ onPress = { toggleExpandableOverlay }
242228 />
243- < Button link iconSource = { Assets . icons . check } useCustomTheme = { useCustomTheme } onPress = { this . onDonePressed } />
229+ < Button link iconSource = { Assets . icons . check } useCustomTheme = { useCustomTheme } onPress = { onDonePressed } />
244230 </ View >
245231 ) ;
246- }
247-
248- renderAndroidDateTimePicker = ( { visible} : RenderCustomOverlayProps ) => {
249- if ( visible ) {
250- return this . renderDateTimePicker ( ) ;
251- }
252232 } ;
253233
254- renderDateTimePicker ( ) {
234+ const renderDateTimePicker = useCallback ( ( ) => {
255235 if ( ! RNDateTimePicker ) {
256236 return null ;
257237 }
258238
259- const { value} = this . state ;
260- const { mode, minimumDate, maximumDate, locale, is24Hour, minuteInterval, timeZoneOffsetInMinutes, themeVariant} =
261- this . props ;
262-
263239 return (
264240 < RNDateTimePicker
265241 mode = { mode }
266242 value = { value || new Date ( ) }
267- onChange = { this . handleChange }
243+ onChange = { handleChange }
268244 minimumDate = { minimumDate }
269245 maximumDate = { maximumDate }
270246 locale = { locale }
@@ -275,40 +251,66 @@ class DateTimePicker extends Component<DateTimePickerPropsInternal, DateTimePick
275251 themeVariant = { themeVariant }
276252 />
277253 ) ;
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+ ] ) ;
284266
267+ const renderIOSExpandableOverlay = ( ) => {
285268 return (
286269 < >
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 ( ) }
307272 </ >
308273 ) ;
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+ ) ;
310311}
311312
313+ DateTimePicker . displayName = 'DateTimePicker' ;
312314export { DateTimePicker } ; // For tests
313315export default asBaseComponent < DateTimePickerProps > ( DateTimePicker ) ;
314316
0 commit comments