_The problem:_
value props is being updated, even if picker is open. The use case is below.
_The component:_
function MyComponent ({ onChange, mode }) {
const [value, setValue] = useState(new Date());
useEffect(() => {
setTimeout(() => setValue(new Date(+ value + 60 * 60 * 1000)), 5000);
}, []);
return (
<DateTimePicker
value={value}
mode={mode}
onChange={onChange}
/>
);
}
_The scenario:_
<MyComponent mode="time" onChange={...} /> is renderedTested only on Android
react-native info output:
System:
OS: Windows 10 10.0.19041
CPU: (8) x64 Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
Memory: 4.55 GB / 15.85 GB
Binaries:
Node: 12.18.0 - ~\AppData\Local\Temp\yarn--1596125151497-0.3420221356958104\node.CMD
Yarn: 1.22.4 - ~\AppData\Local\Temp\yarn--1596125151497-0.3420221356958104\yarn.CMD
npm: 6.14.4 - C:\Program Files\nodejs\npm.CMD
Watchman: Not Found
SDKs:
Android SDK: Not Found
IDEs:
Android Studio: Version 3.6.0.0 AI-192.7142.36.36.6392135
Languages:
Java: Not Found
Python: 2.7.16 - /c/Python27/python
npmPackages:
@react-native-community/cli: Not Found
react: 16.11.0 => 16.11.0
react-native: ~0.62.2 => 0.62.2
npmGlobalPackages:
*react-native*: Not Found
Library version: 2.6.1
hello and thanks for reporting. What is your expected behavior?
I'd say onChange function should be called after each change (at the moment it is called on ok/cancel button press).
This will turn the component into truly controlled one. Simple analogy from html:
const [value, setValue] = useState('');
...
<input value={value} onChange={e => setValue(e.target.value)} />
It seems to me you're talking about 2 separate things
first:
The problem:
value props is being updated, even if picker is open.
second:
onChange function should be called after each change (at the moment it is called on ok/cancel button press).
or do I misunderstand? Please expand on this, and be detailed in the differences between current and expected behavior, thank you.
@alex-golubtsov Ideally, you'd provide a repro in the form of an Expo Snack - community modules work there without issues. (nit: it'd be nice, if you also included the implementation of your onChange function; also, do you need to call useEffect to demonstrate your issue? Might be easier without it)
Is your point that onChange gets called when your timer alters the value, and not when you click 'ok' to confirm that change?
@rectified95 here is the example https://snack.expo.io/@alexg-dev/1abd37
As I described earlier, just try to pick a hour and wait a sec.
@vonovak, take a look on the example. Changing onChange behavior might fix the problem
I left comments directly in the code.
Pls, let me know if I need to be more specific. And thanks for quick responses, guys!
@alex-golubtsov
I think you're misusing the datetimepicker control.
The onChange callback gets called in response to events raised by events on the native platform, ie. user clicks something, native control emits an event across the bridge for JS to receive, RN passes it on to the UI element with the React tag specified by the native control when calling DispatchEvent (or whatever the method's name is internally).
The value prop is used to control the state of the DateTimePicker from JS in a programmatic way, eg. to set an initial value. Just like any other property, if you change its value, it results in a call to the UpdateProperties method in the control's ViewManager on the native side. Usually, you don't want that to result in a JS event being emitted, because that would result in a loop. (JS event emitted in response to value being updated, could potentially call a setter that again modifies value, which in turn results in a JS event...)
To sum up, updates to the value prop will not result in onChange getting invoked, since that's reserved for user interaction with the native element itself.
The snack you sent us does show some wacky behavior - after I make a selection, it gets reverted almost immediately.
Can you explain your use case? Why do you need to set value using a hook, which gets executed repeatedly, every time the render() function gets invoked?
I think once we establish that, we could start thinking about whether there should be separate events for time value selections before clicking 'ok' (I know the native Windows API exposes that functionality, but I didn't make use of it in this module in order to conform with Android/iOS here).
The snack you sent us does show some wacky behavior - after I make a selection, it gets reverted almost immediately.
Yep, this is exactly the problem :)
Can you explain your use case? Why do you need to set
valueusing a hook, which gets executed repeatedly, every time the render() function gets invoked?
I have kind of digital clock app. Something like:
function Clock() {
const timestamp = useTime();
const { hours, minutes, seconds } = parseTimestamp(timestamp);
...
return (
...
<TouchableOpacity onPress={showPicker}>
<Text>{hours}:{minutes}:{seconds}</Text>
</TouchableOpacity>
{pickerVisible && <Picker .... />}
...
);
}
In this example useTime hook is being updated each second, which means whole Clock component is also being updated each second.
It should be possible for user to "jump in time" (+/- a couple of hours) by click on <TouchableOpacity>. And this is where everything breaks. User sees the picker, but cannot select any time from it.
Im also having this problem using the downstream component react-native-modal-datetime-picker. See https://github.com/mmazzarolo/react-native-modal-datetime-picker/issues/451
Ive looked at the code and from what I can tell every render it will call picker.open() which I'm guessing is causing the issue. This should only be called once (on initial render). https://github.com/react-native-community/datetimepicker/blob/master/src/datetimepicker.android.js#L46
Maybe put this inside a useEffect or similar which only executes on initial load (or perhaps value change).
This issue would also be able to be worked around if onChange was called every time that the user clicks a date in the UI (its currently only called on clicking ok). This would mean that the latest value could be passed back into the component (so when it re-called open it would at least do so with the latest date the user selected)
I fixed this error in my code by memoing the component. This means it only renders once.
I fixed this error in my code by memoing the component. This means it only renders once.
I did the same as temporary solution. But be aware, this is potential source of problems.
As React.memo doc says:
This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs.
:tada: This issue has been resolved in version 3.0.9 :tada:
The release is available on:
Your semantic-release bot :package::rocket:
Most helpful comment
Im also having this problem using the downstream component
react-native-modal-datetime-picker. See https://github.com/mmazzarolo/react-native-modal-datetime-picker/issues/451Ive looked at the code and from what I can tell every render it will call
picker.open()which I'm guessing is causing the issue. This should only be called once (on initial render). https://github.com/react-native-community/datetimepicker/blob/master/src/datetimepicker.android.js#L46Maybe put this inside a useEffect or similar which only executes on initial load (or perhaps value change).
This issue would also be able to be worked around if onChange was called every time that the user clicks a date in the UI (its currently only called on clicking ok). This would mean that the latest value could be passed back into the component (so when it re-called open it would at least do so with the latest date the user selected)