react-spectrum
react-spectrum copied to clipboard
DateRangePicker's controlled value can't be set to null to show the placeholder instead of the previous selected date
๐ Bug Report
Value prop can't be set to null (as in the documentation) in DateRangePicker because TypeScript needs a DateValue type of value. Once the value prop has received a value then the value can't be removed completely from the date field to show just a placeholder again.
๐ค Expected Behavior
If the value prop in the DateRangePicker receives a null or undefined value then the previous selected date should be removed from the date field and should display a placeholder again instead of the previous selected date.
๐ฏ Current Behavior
If the value prop in the DateRangePicker receives a null or undefined value then the previous selected date stays there in the date field.
๐ฆ Context
I am trying to reset the DateRangePicker's value to nothing to show the placeholder again after the previously selected date has been removed from the filtering list, so it makes less confusing for the users.
๐ป Code Sample
๐ Your Environment
| Software | Version(s) |
|---|---|
| react-spectrum | 3.17.0 |
| Browser | Google Chrome |
| Operating System | MacOS |
๐งข Your Company/Team
AEM - Admin UI, Odin project
Same issue here. The date range picker considers undefined value as a sign of an uncontrolled component and what's even weirder, seems to revert selection to the first selection ever made (e.g. if the user tries a few different options before attempting to reset the range altogether).
Haven't tested a lot and setting the value to { start: null, end: null } looks ok but in that case Typescript becomes a problem since start and end are not allowed to be null. Ideally that should be possible to enable a 'truly' controlled component.
I guess ValueBase needs to accept null for forcing controlled mode?
export interface ValueBase<T, C = T> {
/** The current value (controlled). */
- value?: T,
+ value?: T | null,
/** The default value (uncontrolled). */
defaultValue?: T,
/** Handler that is called when the value changes. */
onChange?: (value: C) => void
}
I'm trying to repro the issue with the latest version, here's the code sample -
function ControlledResetExample(props) {
let [value, setValue] = React.useState(null);
return (
<Flex direction="column" alignItems="center" gap="size-150">
<DateRangePicker label="Controlled" {...props} value={value} onChange={chain(setValue, action('onChange'))} />
<ActionButton onPress={() => setValue({start: new CalendarDate(2020, 2, 3), end: new CalendarDate(2020, 5, 4)})}>Change value</ActionButton>
<ActionButton onPress={() => setValue(null)}>Clear</ActionButton>
</Flex>
);
}
Seems to me setting value to null works just fine.
@lishichengyan do you have TS strict mode on? that can mess up some of these explicit vs implicit null's
@lishichengyan do you have TS strict mode on? that can mess up some of these explicit vs implicit null's
no, in tsconfig.json I see "strict": false,
I'm trying to repro the issue with the latest version, here's the code sample -
function ControlledResetExample(props) { let [value, setValue] = React.useState(null); return ( <Flex direction="column" alignItems="center" gap="size-150"> <DateRangePicker label="Controlled" {...props} value={value} onChange={chain(setValue, action('onChange'))} /> <ActionButton onPress={() => setValue({start: new CalendarDate(2020, 2, 3), end: new CalendarDate(2020, 5, 4)})}>Change value</ActionButton> <ActionButton onPress={() => setValue(null)}>Clear</ActionButton> </Flex> ); }Seems to me setting
valuetonullworks just fine.
This issue appears to be related to granularity. If you set it to minute then the behaviour becomes weird. I made a repro:
https://codesandbox.io/s/loving-christian-ciisig?file=/src/DateRangePicker.js
-
Press on the calendar and select a date range
-
close it and press reset button
-
if you open the datepicker again, old values are persisted (i would expect them to also get reset)
P.S also calling setValue(null) - errors. (uncomment it in the file to repro)
@lishichengyan if anything is unclear, please let me know
Running into this as well, also without granularity set to minute.
When setting the value to null, it doesn't reset the value and clear the segments. Your example is helpful though, I suppose I need to explicitly call state.setValue to update the internal state, will try it out.
Apologies, I believe I misunderstood this issue the first time I looked at it, thought this was referring to a typescript strict mode issue. I've confirmed the issue where setting the date values to null isn't clearing the the visual range selected in the calendar dropdown when the granularity is set to "minute" or "second".
EDIT: Did some quick digging, looks like we properly update the tracked controlled value in useDateRangeState but not the date range value. I believe we can just call setSelectedDateRange and setSelectedTimeRange with null here at the very least, will need to move the state initialization up before that section
I'm having the same issue with useDatePicker and TypeScript on strict mode. null is not allowed, and using undefined instead leaves the hook in an uncontrolled state, behaving differently.
Does anyone have any workaround?
I'm having the same issue with
useDatePickerand TypeScript on strict mode.nullis not allowed, and usingundefinedinstead leaves the hook in an uncontrolled state, behaving differently.Does anyone have any workaround?
I ended up having to @ts-ignore those places to be able to use null ๐จ Doesn't feel good, but hopefully just a temporary solution until things get fixed.
@christofferok thanks, @christofferok - that's what I did too. ๐
As a workaround, I used pnpm patch functionality to patch the type for the time being.
diff --git a/src/inputs.d.ts b/src/inputs.d.ts
index 653b30af794558acb854a4fab593f514e8487d05..18e7ec3cf4d63537327f1f857c78a8f85ab16a02 100644
--- a/src/inputs.d.ts
+++ b/src/inputs.d.ts
@@ -33,7 +33,7 @@ export interface InputBase {
export interface ValueBase<T, C = T> {
/** The current value (controlled). */
- value?: T,
+ value?: T | null,
/** The default value (uncontrolled). */
defaultValue?: T,
/** Handler that is called when the value changes. */
You can use patch-package and other alternatives to achieve the same thing for other package managers.
The TypeScript strict mode part of this was addressed in #4225. There may be also a remaining behavioral issue mentioned by @LFDanLu above, which we will look at in an upcoming sprint.
@devongovett This works great for setting value outside of the component itself. But referencing state from inside of the component doesn't allow for setting null on any of the values.
const handleCalendarDateInputKeyDown = (
e: React.KeyboardEvent,
segment?: 'start' | 'end'
) => {
const { key } = e;
switch (key) {
case 'Escape':
state.setDateTime(segment, null as unknown as DateValue);
return;
}
};
receives this error
Cannot destructure property 'hour' of '(intermediate value)' as it is undefined.
I've tried bunch of combinations. Closest I got was
const handleCalendarDateInputKeyDown = (
e: React.KeyboardEvent,
segment?: 'start' | 'end'
) => {
const { key } = e;
switch (key) {
case 'Escape':
const nullState = {
year: null,
month: null,
day: null,
hour: null,
minute: null,
second: null,
millisecond: null,
};
if (segment === 'start') {
state.setDateTime(segment, nullState as unknown as DateValue);
}
return;
}
};
But this gives
value.compare is not a function
Best way to reset value from internally using the state object returned by the hook?