wix / react-native-calendars

React Native Calendar Components 🗓️ 📆
MIT License
9.52k stars 2.95k forks source link

Fast swiping in `CalendarList` causes infinite loop of switching months #2353

Open carlbleick opened 11 months ago

carlbleick commented 11 months ago

Description

Swiping (very) fast in a CalendarList or in an expanded ExpandableCalendar causes the component to loop infinitely between the two months.

Expected Behavior

I swiped twice to the left (or right) in a CalendarList with little to no delay in between the swiping gestures. I expected the calendar to move two months in the future (or past) or ignore the second swipe gesture.

Observed Behavior

The calendar kept switching between the two months infinitely.

Environment

Please run these commands in the project folder and fill in their results:

Also specify:

  1. Google Pixel 6 Pro - Android 14

It also happens on other android and iOS devices. It does not happen on the new iPhone 15 Pro (because its fast?).

Reproducible Demo

Please provide a minimized reproducible demonstration of the problem you're reporting.

Screenshots

Screenshots or gifs of the issue and the suggested fix will help us move faster with the review process.

KalAvroniev commented 10 months ago

I have react-native-calendars@1.1302.0 on react-native@0.67.4 and this issue is not present.

It might have something to do with the new react-native and its JS engine.

I have another app currently under development with the same calendar version but a different react-native@0.72.7. I have the same issue with the bare minimum setup and no event handlers or data retrieval.

carlbleick commented 10 months ago

@KalAvroniev good to know I'm not alone with that issue. NIce observation that the bug is only occurring with a newer react-native version (>0.70 or >0.71?).

I already tried to debug it but was not able pinpoint the issue. I noticed that the loop does not start when I comment out the setDate() provided by the CalendarContext.Provider. Other functions would obviously get disabled by that but it was one of the few things that actually prevented the infinite loop.

KalAvroniev commented 10 months ago

@carlbleick I played around the calendar a bit more and what I identified was to do with the onDateChanged and onMonthChange. For some reason when you change months, the _setDate function would always call one or both and that was causing state changes on my parent component, causing the calendar to reload and produce new functions for those. And that was causing the crazy behaviour. What we tried doing was delaying the state changes for the parent and calendar props until after all _setDate calls are done by debouncing the onDateChanged and onMonthChange.

Here is the custom code we used to debounce the two events

export const useDebounceFn = (fn: Function, wait: number) => {
  const timeout = useRef(null);

  return (...args) => {
    if (timeout.current) {
      clearTimeout(timeout.current);
    }
    timeout.current = setTimeout(() => fn(...args), wait);
  };
};

export const useDebouncedState = (initialValue: any, wait: number) => {
  const [value, setValue] = useState(initialValue);
  return [value, useDebounceFn(setValue, wait)];
};

Hope this helps. It seems to bring some stability to the calendar for us.

pasanediri97 commented 10 months ago

I am experiencing something similar in Agenda in the same version react-native-calendars@1.1302.0 react-native": "0.72.1 when i just scroll the reservation list little bit fast, its just switching months in calendar list and scrolling infinitely.

carlbleick commented 7 months ago

@KalAvroniev I just added your debounce fix and it worked. Interestingly I only used and debounced the onMonthChange callback and it still fixes the problem. Sadly by delaying the changes the swipe controls of the calendar are even more slow, but I still prefer this over the infinite loop.

poinch commented 2 months ago

@carlbleick where did you placed the code provided from @KalAvroniev? Did you patch the library?

carlbleick commented 2 months ago

@poinch I added the debounce inside my own code, like this:

// debounce the callback to prevent an infinite swiping bug:
// https://github.com/wix/react-native-calendars/issues/2353#issuecomment-1844676692
const onMonthChanged = useCallback(
    debounce((date: DateData) => {
        // do stuff
    }, 0),
    [depA, depB]
);

// and

<ExpandableCalendar
    onMonthChange={onMonthChanged}
    // rest..
/>
poinch commented 2 months ago

@carlbleick Nice, thank you, it worked