datetimepicker icon indicating copy to clipboard operation
datetimepicker copied to clipboard

iOS DateTimePicker in `countdown` mode fails to call `onChange` on first update

Open elsieclark opened this issue 4 years ago • 9 comments

Bug

In countdown mode, the first time the value in the picker gets updated, the onChange event isn't triggered. Every subsequent time, it is. The date, time, and datetime modes work fine.

I found the following Stack Overflow thread which describes exactly the same problem, except just in Swift, no React Native. This leads me to think there's a problem with the underlying Swift API.

For anyone else who runs into this problem, I was able to get around it by updating the value prop at least once upon initialization, before the user has time to interact with it. Even changing the value date by a single second seems to do the trick; I recommend that to avoid playing an animation on the update.

Workaround:

  value={this.state.initialized ? new Date(0, 0, 0, hours, minutes, seconds) : new Date(0, 0, 0, hours, minutes, seconds+1)}

Environment info

System:
    OS: macOS 10.14.6
    CPU: (8) x64 Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz
    Memory: 680.85 MB / 16.00 GB
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 10.15.3 - /usr/local/opt/nvm/versions/node/v10.15.3/bin/node
    Yarn: 1.12.3 - /usr/local/bin/yarn
    npm: 6.9.2 - /usr/local/opt/nvm/versions/node/v10.15.3/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  SDKs:
    iOS SDK:
      Platforms: iOS 13.0, DriverKit 19.0, macOS 10.15, tvOS 13.0, watchOS 6.0
  IDEs:
    Xcode: 11.0/11A420a - /usr/bin/xcodebuild
  npmPackages:
    react: 16.8.6 => 16.8.6 
    react-native: 0.60.3 => 0.60.3 
  npmGlobalPackages:
    react-native-cli: 2.0.1

Library version: 2.1.0

Steps To Reproduce

  1. Add a DateTimePicker to your app
  2. Set mode prop to countdown
  3. Set onChange prop to something that should produce visible output
  4. Load up the component in your App. Slide the picker to a new value (First change). The onChange handler will not trigger.
  5. Slide the picker to another new value (Second change). The onChange handler will trigger. ...

Describe what you expected to happen:

  1. The onChange handler should update both times.

Reproducible sample code

<DateTimePicker
    mode={'countdown' as any}
    onChange={(event, date) => console.log('Updated!')}
    value={new Date(0, 0, 0, hours, minutes, seconds)}
/>

elsieclark avatar Sep 26 '19 03:09 elsieclark

@willdavidc Thanks for this workaround. It helps a lot, but unfortunately doesn't fix the problem completely. If the user spins the picker to 0 hours 0 minutes, iOS automatically changes it to 0 hours 1 minute, and onChange again doesn't fire the next time the user moves the picker.

Example code here.

chetstone avatar Jan 19 '20 00:01 chetstone

@Swaagie Could someone take a look at this bug? The workaround suggested by @willdavidc helps, though it took me a lot of trial and error to figure out where to set and reset this.state.initialized. My result is shown in this PR for another component but the owner of that library quite reasonably does not want to merge the workaround. It needs to be fixed here, probably in native code, which I'm not particularly proficient at.

Thanks!

chetstone avatar Jan 21 '20 01:01 chetstone

I've switched to react-native-countdown-picker. It doesn't have this problem. Although the library hasn't been updated in a long time, it is a very simple wrapper around the ActionSheetPicker-3.0 pod, which is actively maintained.
If you install from my PR (not sure when or if it'll ever get merged), you'll get auto-linking in RN >= 0.60.0

yarn add 4Catalyzer/react-native-countdown-picker#pull/3/head

chetstone avatar Jan 28 '20 22:01 chetstone

The workaround doesn't work for me... Any news on this? For the time being I've switched to: https://github.com/uraway/react-native-simple-time-picker

dylancom avatar Mar 04 '20 20:03 dylancom

A bit frustrating, but I have the workaround working for me. I'm using hooks and this is how I've set it up.

Not amazing, but it's working well enough.

  // Variable to hold the time, set it to 1 minute
  const [duration, setDuration] = useState(new Date(0, 0, 0, 0, 1, 0, 0))
  const [hasLoaded, setHasLoaded] = useState(false)
  useEffect(() => {
    setTimeout(() => {
      setHasLoaded(true)
    }, 50) // I needed to include the timeout for this to work
  }, [])
  //...

  <DateTimePicker
  ...
  value={hasLoaded ? duration : new Date(0, 0, 0, 0, 1, 5)}
  /> 
  // Other time probably doesn't have to be 5 seconds, but that's what I did

connorlindsey avatar May 05 '20 18:05 connorlindsey

A bit frustrating, but I have the workaround working for me. I'm using hooks and this is how I've set it up.

Not amazing, but it's working well enough.

  // Variable to hold the time, set it to 1 minute
  const [duration, setDuration] = useState(new Date(0, 0, 0, 0, 1, 0, 0))
  const [hasLoaded, setHasLoaded] = useState(false)
  useEffect(() => {
    setTimeout(() => {
      setHasLoaded(true)
    }, 50) // I needed to include the timeout for this to work
  }, [])
  //...

  <DateTimePicker
  ...
  value={hasLoaded ? duration : new Date(0, 0, 0, 0, 1, 5)}
  /> 
  // Other time probably doesn't have to be 5 seconds, but that's what I did

this worked for me. Thanks a lot!

pvanny1124 avatar Jan 22 '21 10:01 pvanny1124

A bit frustrating, but I have the workaround working for me. I'm using hooks and this is how I've set it up.

Not amazing, but it's working well enough.

  // Variable to hold the time, set it to 1 minute
  const [duration, setDuration] = useState(new Date(0, 0, 0, 0, 1, 0, 0))
  const [hasLoaded, setHasLoaded] = useState(false)
  useEffect(() => {
    setTimeout(() => {
      setHasLoaded(true)
    }, 50) // I needed to include the timeout for this to work
  }, [])
  //...

  <DateTimePicker
  ...
  value={hasLoaded ? duration : new Date(0, 0, 0, 0, 1, 5)}
  /> 
  // Other time probably doesn't have to be 5 seconds, but that's what I did

worked for me but not very convenient as said above

ingerable avatar Apr 05 '21 09:04 ingerable

Thanks for this - I had to use it only in iOS 15.

In my case, because my picker was in a modal, I have to do the "workaround" every time the modal was toggled.

hugows avatar Feb 28 '23 15:02 hugows