gpbl / react-day-picker

DayPicker is a customizable date picker component for React. Add date pickers, calendars, and date inputs to your web applications.

Home Page:https://daypicker.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Bug: dropdown with two month updating the first month only

fotopixel opened this issue · comments

Bug description

If i use numberOfMonths={2} with and display a dropdown for the month if have a bug when i change the toMonth. This will set the fromMonth and not the toMonth.

To reproduce

https://codesandbox.io/s/react-daypicker-forked-vs4qvd?file=/src/App.tsx:189-207

Steps

  1. change to fromMonth to March
  2. change the toMonth in the second Month to December
  3. have a look to the fromMonth it will change to the selected toMonth

Expected behavior

If I change the month for a displayed month, all other Months should be alight to see selected Month.

For example:

selecting in the second Month December, i would expect that the first Month will become November and not December

@fotopixel I'm not understanding your issue. What is the current behavior - what is the expected behavior?

@gpbl sorry.

ok let's try to describe it more clear.

if you set the number of displayed month to 2 or even more, you will get the described issue.

Current behavior:

the picked month (from the right side) is selected in the left calendar, and the right calendar shows the following month

Bildschirmaufnahme.2023-07-26.um.15.22.56.mov

expected behavior:

The month which I select in the calendar should also be displayed in the same.

If I select in the right calendar December it should be displayed in the right calendar

Thanks @fotopixel, now I got it! Yeah, I understand this is unexpected. You'd see the calendar under the second dropdown change to the selected month – instead is the first being updated.

Still getting familiar with the codebase, but I think it's just because we're changing the single month state variable here

export function useNavigationState(): NavigationState {
const context = useDayPicker();
const initialMonth = getInitialMonth(context);
const [month, setMonth] = useControlledValue(initialMonth, context.month);
const goToMonth = (date: Date) => {
if (context.disableNavigation) return;
const month = startOfMonth(date);
setMonth(month);
context.onMonthChange?.(month);
};
return [month, goToMonth];
}

which is then used over here

const [currentMonth, goToMonth] = useNavigationState();
const displayMonths = getDisplayMonths(currentMonth, dayPicker);

First solution that came to mind was keeping track of another state variable that tracks which month calendar was changed last as an index (0, 1, 2, 3, ...). So now, if the user changes the second month calendar, we'd know to set the second month in the calculated array to the newly selected month, and we can calculated the previous month for the first calendar in the array, etc.

@IDrumsey I'm currently working on the next version that would for sure make this easy to fix. The underlying issue is the navigation is rendered per each month – while it should be just one.

You could try with a custom component replacing the Caption, but it may get tricky.

https://codesandbox.io/s/rktq65

Hey, I had to fix this to be able to ship a feature into production so I made a little hack to fix the behavior. It's far from perfect but it's a quick fix before something more permanent.

Please note that this will work only if you didn't change the class names (or you need to specify your custom one) and only if you have only one DayPicker on the page.

If you have numberOfMonths prop > 2 you could map the event listener to the other elements starting from [1] index

 // When selecting a value shift the target by one, so that the correct month is selected
 const unshiftMonth = (ev: Event) => {
    if (!ev.target) return;
    const target = ev.target as HTMLSelectElement;
    ev.preventDefault();
    target.value = `${Number(target.value) - 1}`;
  }

  useEffect(()=>{
    const secondCalendarMonthEl = document.getElementsByClassName("rdp-dropdown_month")[1] as HTMLSelectElement;

    if (secondCalendarMonthEl) secondCalendarMonthEl.addEventListener("change", (unshiftMonth))

    return () => {
      if (secondCalendarMonthEl) secondCalendarMonthEl.removeEventListener("change", unshiftMonth)    
    }

  },[])

@xskipy thanks for the update and for sharing your workaround.

I could implement the fix in #1884 (I would appreciate a quick review there). Publishing it later in the day...