react-spectrum icon indicating copy to clipboard operation
react-spectrum copied to clipboard

bug: `useDatePicker` sometimes limits day of month to 30

Open jordanoverbye opened this issue 2 years ago โ€ข 4 comments

๐Ÿ› Bug Report

This bug can be reproduced using the interactive demos on the useDatePicker documentation page.

When I first interact with the component I'm unable to enter any dates which have 31 as the day of the month. When I type in 31, my text is cleared and the value is set to 01.

๐Ÿค” Expected Behavior

A user should be able to enter dates that have 31 as the day of the month, for example 31st Jan 2022.

๐Ÿ˜ฏ Current Behavior

  1. Focus into the day segment
  2. Type in 31
  3. Day segment shows 01
  4. Enter in a month and day to get a valid date
  5. Change day of month from 01 to 31

๐Ÿ’ Possible Solution

  • Looks like something in useDateFieldState is the issue, but haven't had time to investigate.
  • Looks like aria-valuemax="30" is set when the component is first rendered, but after some user interaction it gets set to aria-valuemax="31"

๐Ÿ”ฆ Context

๐Ÿ’ป Code Sample

You can also see this issue in the "styled" code sandbox examples of useDatePicker.

  • https://codesandbox.io/s/reverent-faraday-5nwk87?file=/src/DateField.js
  • https://codesandbox.io/s/white-breeze-rer8g2?file=/src/DateField.js

๐ŸŒ Your Environment

Software Version(s)
react-spectrum "@react-aria/datepicker": "3.0.0"
Browser Chrome
Operating System Mac

Full list of dependencies can be found in the package.json https://codesandbox.io/s/reverent-faraday-5nwk87?file=/package.json

๐Ÿงข Your Company/Team

N/A

๐Ÿ•ท Tracking Issue (optional)

N/A

jordanoverbye avatar Jun 22 '22 10:06 jordanoverbye

This is because there's always a date under the hood, even when the placeholder is visible. It defaults to today's date, but can be set via the placeholderValue prop as well. You can see it if you focus a segment and press the up or down arrow keys. So, you might not be able to type 31 if the month of the placeholder only has 30 days, for example. Perhaps a little confusing.

The alternative would be to allow 31 (or the maximum across all months in a given calendar system), but then if you enter a month with fewer days later, adjust it. But the number of days in a month might still depend on the year in case of leap years, so even that might be wrong. The actual minimums and maximums can't be determined until a full date is entered, so right now we default it to today's date (or the placeholder value if provided). Not totally sure what the best behavior would be... ๐Ÿค”

devongovett avatar Jun 22 '22 17:06 devongovett

I'm sorry to intrude but this issue is really exacerbated when the date format is dd-mm-yyyy, since you have to type the day before the month.

I got around it by using the placeholder value as @devongovett suggested ๐Ÿ™, which I initialised as placeholderValue: toCalendarDate(new CalendarDateTime(2024, 1, 31)) (a leap year), but I was wondering if there was a more proper way to do this.

RaspberryEuphoria avatar Jun 27 '22 13:06 RaspberryEuphoria

We discussed this as a team and we agree that the behavior should change: allow the maximum value across all months to be entered, and only constrain to the actual maximum once all fields have been entered.

This is slightly challenging because the value is always currently represented as a date even when all fields haven't yet been entered. Therefore the manipulation logic such as incrementing, decrementing, etc. is done using a date object, which only supports valid dates. So we'd need to replicate some of this logic outside the date library so it happens on each field independently instead. This will take some refactoring.

devongovett avatar Jun 29 '22 21:06 devongovett

I'd like to point out that there are two slightly different scenarios that are affected: entering a date, and replacing a date. Both have bad UX at the moment with ddmmyyyy.

Let's say we want to enter the date 31.10.2022 (October 31th, 2022).

This is how it should work in my opinion:

[  ] [  ] [    ] <- initial state
[31] [  ] [    ] <- partial date
[31] [10] [    ] <- partial date
[31] [10] [2022] <- finished!

As far as I understand, this is what the UX would be after the suggested behaviour change. But then we have the other scenario: let's imagine we already have 01.02.2020 (February 1st, 2020) and intend to replace the whole date.

I think this is how it should work optimally from an UX point of view, at least for users using a keyboard:

[01] [02] [2020] <- initial state
[31] [02] [2020] <- full, but invalid date
[31] [10] [2020] <- full, but wrong date
[31] [10] [2022] <- finished!

The problem is that from the user's point of view entering the date is still in progress after they've typed "31", but technically we have an invalid (not partial!) date at that point and the situation is only resolved after the month has been changed. As far as I understand, the suggested behaviour change would not necessarily fix this, because it's ambiguous what "all fields have been entered" means when we have a mix of new user input and some parts of the previous value.

Gekkio avatar Sep 01 '22 11:09 Gekkio

Since we are in February now, the example here https://react-spectrum.adobe.com/react-spectrum/DateField.html also jumps directly to the month once I enter "3" in the day field

gopeter avatar Feb 03 '23 13:02 gopeter

Same issue here with dd/mm/yyyy, the behavior is very weird when setting the initial date or replacing a date. Is there any plans on fixing this?

fknop avatar Apr 13 '23 08:04 fknop

Yes, we'd like to address this. We haven't had the time to get to it yet, but we do accept contributions. Otherwise, if you could 'thumbs up' the issue description, we do check for those when deciding what bugs to prioritize.

snowystinger avatar Apr 13 '23 17:04 snowystinger

@RaspberryEuphoria 's workaround works well. Only issue is that the placeholder will dictate which date is selected when you open the calendar. To avoid always opening on 1/1/2024 I have implemented onOpenChange to switch back to the current date when the calendar is opened.

const defaultPlaceholderValue = new CalendarDate(2024, 1, 1);

const [placeholderValue, setPlaceholderValue] = useState(defaultPlaceholderValue);
  
<DatePicker
  placeholderValue={placeholderValue}
  ...
  onOpenChange={(isOpen: boolean) => {
    if (isOpen) {
      const today = new Date();
      const day = getDate(today);
      const month = getMonth(today) + 1; // +1 because months are 0-indexed
      const year = getYear(today);
      setPlaceholderValue(new CalendarDate(year, month, day));
    } else {
      setPlaceholderValue(defaultPlaceholderValue);
    }
  }}
>

marano avatar Nov 24 '23 17:11 marano