BugiDev / react-native-calendar-strip

Easy to use and visually stunning calendar component for React Native.
MIT License
939 stars 327 forks source link

MarkedDates only rendering when selecting day #189

Closed guibas741 closed 4 years ago

guibas741 commented 4 years ago

I have an application that marks days according to the response of the api, but every time I change the week and call the api again I need to select a day so the dots appears on the screen. week

Can someone help me? I tried to set the selected as the first day of the next week but couldn't do that either.

peacechen commented 4 years ago

That looks like a bug. Please post your <CalendarStrip> code or a Snack.

I tested markedDates in the example app in this repo and it's working as expected in scrollable and non-scrollable modes. Are you dynamically updating the prop as the weeks change?

guibas741 commented 4 years ago

@peacechen sorry about the late response.

This is my code:

const WeekCalendarStrip = ({ selectedDate, onWeekChanged, onDateSelected, activities }) => {
const formattedDaysOfMonth = useMemo(
    () =>
      activities.reduce((acc, activity) => {
        const [activityDate, activityItems] = activity;

        const dots = activityItems.reduce((accDots, item) => {
          const hasDot = accDots.find(dotItem => dotItem.key === DOTS_STYLES[item.type].key);

          if (!hasDot) {
            accDots.push({
              key: DOTS_STYLES[item.type].key,
              color: theme.colors[DOTS_STYLES[item.type].color],
              selectedColor: theme.colors[DOTS_STYLES[item.type].selectedColor]
            });
          }
          return accDots;
        }, []);

        acc.push({ date: activityDate, dots });
        return acc;
      }, []),
    [activities, theme.colors]
  );

return (
<CalendarStrip
      showMonth={false}
      locale={CALENDAR_STRIP_LOCALE[locale]}
      leftSelector={<ChangeWeekIcon name="chevron-left" />}
      rightSelector={<ChangeWeekIcon name="chevron-right" />}
      customDatesStyles={customSundayStyle}
      daySelectionAnimation={daySelectedStyle}
      highlightDateNameStyle={calendarStripStyle}
      highlightDateNumberStyle={calendarStripStyle}
      markedDates={formattedDaysOfMonth}
      onDateSelected={onDateSelected}
      onWeekChanged={onWeekChanged}
      selectedDate={selectedDate}
    />
);
}

I receive onDateSelected, onWeekChanged and selectedDate as props to my component

peacechen commented 4 years ago

This looks like an issue with useMemo. As you may know, it optimizes by caching results when the inputs are the same.

Try to move the anonymous function outside of useMemo and pass it directly to CalendarStrip. If that works, debug why useMemo isn't returning the proper value when the week changes. I suspect you may need to add a variable to the useMemo watch list (the line with [activities, theme.colors]).

guibas741 commented 4 years ago

I removed the useMemo but still occurs the same problem, the dots only appears when I select a day. If I set a static dots array It works, so I think the problem is that when the dots array changes is not rerendering the object.

In fact even when I change dynamically the selectedDate prop the component doesnt rerender, I always need to click on the date so the style is applied.

guibas741 commented 4 years ago

So I found a solution that works for me.

I was using the CalendarStrip as a header to a list like this. The WeekCalendarStripwas my custom CalendarStrip component:

<>
  <WeekCalendarStrip
            onDateSelected={handleDateSelected}
            selectedDate={selectedDate}
            activities={filteredActivities}
            onWeekChanged={handleWeekChange}
            loading={loading}
          />
<FlatList
        ref={listRef}
        data={filteredActivities}
        keyExtractor={item => item[0]}
        renderItem={renderItem}
        initialNumToRender={7}
        maxToRenderPerBatch={7}
        ListEmptyComponent={renderEmptyState}
        onScrollToIndexFailed={() => {}}
      />
</>

Since I wanted to be fixed on the top of the list I render the CalendarStrip as a ListHeader and add the prop stickyHeaderIndices={[0]} to set the Calendar fixed on top.

<FlatList
        ref={listRef}
        data={filteredActivities}
        keyExtractor={item => item[0]}
        ListHeaderComponent={() => (
          <WeekCalendarStrip
            onDateSelected={handleDateSelected}
            selectedDate={selectedDate}
            activities={filteredActivities}
            onWeekChanged={handleWeekChange}
            loading={loading}
          />
        )}
        renderItem={renderItem}
        stickyHeaderIndices={[0]}
        initialNumToRender={7}
        maxToRenderPerBatch={7}
        ListEmptyComponent={renderEmptyState}
        onScrollToIndexFailed={() => {}}
      />

This way everytime the data prop changes the component will rerender and works just as expected. Besides that couldn't find any other way.

Maybe was not a bug and I wasn't using the lib the correct way, idk. Thanks for the attention and help @peacechen

peacechen commented 4 years ago

@guibas741 Glad that you found a work-around for the issue. I'm attempting to recreate the issue in the sample app to debug.

peacechen commented 4 years ago

I adapted the example with useMemo and it renders the marked dates properly when switching weeks. There must be a difference in the way that the data is updated as the week changes. Note that the activities prop passed to the memo watch list will not trigger a recalculation if the array is the same instance, even if the values within it changes. filteredActivities should be created fresh any time it changes.

peacechen commented 4 years ago

2.0.2 adds support for callbacks to the markedDates prop. An example usage is in the Readme: https://github.com/BugiDev/react-native-calendar-strip#markeddatesformat-example

A callback may be a better fit for useMemo.

gektus commented 4 years ago

This issue is still reproduced even when markedDates is a fn.

When we pass markedDates as a function, rerender will not be caused. The Strip component checks markedDates immediately after I click the left or right button.

But my flow is like this:

  1. Click the left button
  2. Component checks dates with old markedDates
  3. Actual markedDates come from server
  4. Called useMemo hook (or create a new markedDates fn)
  5. Expected rerender, got no changes

I tried to use @guibas741 solution. In my case I tried to render FlatList with empty body.

<FlatList
            data={markedDates}
            ListHeaderComponent={() => (
                <Strip
                    startingDate={currentWeek.startOf('week')}
                    selectedDate={selectedDate}
                    onDateSelected={changeSelectedDate}
                    style={styles.calendar}
                    calendarHeaderStyle={styles.headingText}
                    dateNumberStyle={styles.dateNumberStyle}
                    dateNameStyle={{ color: Colors.Primary }}
                    highlightDateNumberStyle={{ color: Colors.White }}
                    highlightDateNameStyle={{ color: Colors.White }}
                    weekendDateNameStyle={{ color: Colors.White }}
                    calendarAnimation={null}
                    daySelectionAnimation={daySelectionAnimation}
                    customDatesStyles={customDatesStyles}
                    markedDates={markedDates}
                    onWeekChanged={handleWeekChange}
                />
            )}
            stickyHeaderIndices={[0]}
            renderItem={() => null}
        />

It rerenders when data changes. But it also rerenders when I select a date.

It is not elegant, but it covers my needs for now.

gektus commented 4 years ago

I found a better solution. We should call updateWeekView manually when got required markedDates:

const ref = useRef<{ updateWeekView: (date: Moment) => void }>(null);
useEffect(() => {
  if (markedDates && markedDates?.length > 0) {
    ref.current?.updateWeekView(currentWeek);
  }
}, [currentWeek, markedDates]);

return (
  <Strip
    ref={ref}
    startingDate={moment().startOf('week')}
    selectedDate={selectedDate}
    onDateSelected={changeSelectedDate}
    markedDates={markedDates}
    onWeekChanged={handleWeekChange}
  />
);
peacechen commented 4 years ago

@AMaklakov I couldn't recreate the bug with useMemo in the example app. Would you try adapting your code in there? It's inside the example folder in this repo. Another option would be to recreate it in a Snack.

I'm glad that you found a work around using the updateWeekView method. It would be better to fix any issues within the library so that work around isn't needed.

Edit: I can see how the bug could manifest if markedDates data changes after the week changes while it's passed as a function. Aside from updateWeekView, another way to address that is to get the previous and next week's marked dates data when showing the current week. That way the markedDates data is already available when the left/right button is pressed. Another way to deal with this is to pass markedDates as an array. CalendarStrip detects when the markedDates prop changes and updates itself.

JarriusMadeIt commented 3 years ago

Has this issue been fixed yet? I have tried all the above solutions and none of them solve the issue. Marked dates only show up after I press on any of the days in the calendar strip exactly the same as @guibas741. Is the solution as simple as re-rendering the component internally whenever marked dates are changed? This issue needs to be reopened since no solution is yet found. @peacechen Calendar strip does not detect when the marked dates prop changes. Only when pressing on a single day in the visible week triggers the re-render to show the marked dates. Also why does the onWeekChanged method invoked when pressing a day? I think it should only trigger onWeekChanged when the week changes by pressing the right and left buttons, futhermore, It should automatically re-render when marked dates change. Which is the expected behaviour.

Edit: For anyone who comes across the issue and reading this right now. set scrollable={true}, this results in the expected behavior until the issue is fixed for non scrollable calendar strip.

peacechen commented 3 years ago

markedDates along with other relevant props should be added to the prop detection list. Please submit a PR to fix that.

peacechen commented 3 years ago

This fix has been published in 2.1.5.