diff --git a/__test__/components/Calendar.test.tsx b/__test__/components/Calendar.test.tsx
index 3e187f3..2e8a684 100644
--- a/__test__/components/Calendar.test.tsx
+++ b/__test__/components/Calendar.test.tsx
@@ -581,4 +581,188 @@ describe("calendar Availability search", () => {
expect(startDate.getTime()).toBeLessThanOrEqual(viewStart.getTime());
expect(endDate.getTime()).toBeGreaterThanOrEqual(viewEnd.getTime());
});
+
+ describe("Batch loading and prefetching", () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("loads active calendars in batches of 5", async () => {
+ const manyCalendars = Array.from({ length: 12 }, (_, i) => ({
+ [`user1/cal${i + 1}`]: {
+ name: `Calendar ${i + 1}`,
+ id: `user1/cal${i + 1}`,
+ color: { light: "#FF0000", dark: "#000" },
+ events: {},
+ },
+ })).reduce((acc, cal) => ({ ...acc, ...cal }), {});
+
+ const stateWithManyCalendars = {
+ ...preloadedState,
+ calendars: {
+ ...preloadedState.calendars,
+ list: manyCalendars,
+ },
+ };
+
+ const spy = jest
+ .spyOn(calendarThunks, "getCalendarDetailAsync")
+ .mockImplementation(() => ({
+ type: "getCalendarDetailAsync",
+ unwrap: () => Promise.resolve({}),
+ })) as any;
+
+ await act(async () => {
+ renderWithProviders(
+ ,
+ stateWithManyCalendars
+ );
+ });
+
+ await waitFor(
+ () => {
+ expect(spy).toHaveBeenCalled();
+ },
+ { timeout: 3000 }
+ );
+
+ const callCount = spy.mock.calls.length;
+ expect(callCount).toBeGreaterThan(0);
+ expect(callCount).toBeLessThanOrEqual(24);
+ });
+
+ it("prefetches hidden calendars only after active load completes", async () => {
+ const calendarsWithSelected = {
+ ...preloadedState,
+ calendars: {
+ ...preloadedState.calendars,
+ list: {
+ "user1/cal1": {
+ name: "Selected Calendar",
+ id: "user1/cal1",
+ color: { light: "#FF0000", dark: "#000" },
+ events: {},
+ },
+ "user1/cal2": {
+ name: "Hidden Calendar 1",
+ id: "user1/cal2",
+ color: { light: "#00FF00", dark: "#000" },
+ events: {},
+ },
+ "user1/cal3": {
+ name: "Hidden Calendar 2",
+ id: "user1/cal3",
+ color: { light: "#0000FF", dark: "#000" },
+ events: {},
+ },
+ },
+ },
+ };
+
+ const spy = jest
+ .spyOn(calendarThunks, "getCalendarDetailAsync")
+ .mockImplementation(() => ({
+ type: "getCalendarDetailAsync",
+ unwrap: () => Promise.resolve({}),
+ })) as any;
+
+ await act(async () => {
+ renderWithProviders(
+ ,
+ calendarsWithSelected
+ );
+ });
+
+ await waitFor(
+ () => {
+ expect(spy).toHaveBeenCalled();
+ },
+ { timeout: 3000 }
+ );
+
+ const selectedCalls = spy.mock.calls.filter(
+ (call) => call[0].calId === "user1/cal1"
+ );
+ const hiddenCalls = spy.mock.calls.filter(
+ (call) =>
+ call[0].calId === "user1/cal2" || call[0].calId === "user1/cal3"
+ );
+
+ expect(selectedCalls.length).toBeGreaterThan(0);
+ });
+
+ it("does not make duplicate API calls for same calendar and range", async () => {
+ const spy = jest
+ .spyOn(calendarThunks, "getCalendarDetailAsync")
+ .mockImplementation(() => ({
+ type: "getCalendarDetailAsync",
+ unwrap: () => Promise.resolve({}),
+ })) as any;
+
+ await act(async () => {
+ renderWithProviders(
+ ,
+ preloadedState
+ );
+ });
+
+ await waitFor(
+ () => {
+ expect(spy).toHaveBeenCalled();
+ },
+ { timeout: 3000 }
+ );
+
+ const callsForCal1 = spy.mock.calls.filter(
+ (call) => call[0].calId === "user1/cal1"
+ );
+
+ const uniqueRanges = new Set(
+ callsForCal1.map(
+ (call) => `${call[0].match.start}_${call[0].match.end}`
+ )
+ );
+
+ expect(uniqueRanges.size).toBeLessThanOrEqual(callsForCal1.length);
+ });
+
+ it("handles undefined calendars gracefully", async () => {
+ const stateWithUndefinedCalendars = {
+ ...preloadedState,
+ calendars: {
+ list: undefined as any,
+ templist: undefined as any,
+ pending: false,
+ },
+ };
+
+ await act(async () => {
+ renderWithProviders(
+ ,
+ stateWithUndefinedCalendars
+ );
+ });
+
+ expect(screen.getByText("calendar.personal")).toBeInTheDocument();
+ });
+
+ it("handles undefined tempcalendars gracefully", async () => {
+ const stateWithUndefinedTemp = {
+ ...preloadedState,
+ calendars: {
+ ...preloadedState.calendars,
+ templist: undefined as any,
+ },
+ };
+
+ await act(async () => {
+ renderWithProviders(
+ ,
+ stateWithUndefinedTemp
+ );
+ });
+
+ expect(screen.getByText("calendar.personal")).toBeInTheDocument();
+ });
+ });
});
diff --git a/__test__/features/Calendars/CalendarSlice.test.tsx b/__test__/features/Calendars/CalendarSlice.test.tsx
index 4e90b50..ef94ee7 100644
--- a/__test__/features/Calendars/CalendarSlice.test.tsx
+++ b/__test__/features/Calendars/CalendarSlice.test.tsx
@@ -140,6 +140,203 @@ describe("CalendarSlice", () => {
expect(state.list).toEqual({});
});
+ it("getCalendarsListAsync loads user details in parallel for multiple owners", async () => {
+ const mockCalendars = [
+ {
+ _links: { self: { href: "/calendars/u1/cal1.json" } },
+ "dav:name": "Calendar 1",
+ "apple:color": "#FF0000",
+ acl: [],
+ },
+ {
+ _links: { self: { href: "/calendars/u2/cal2.json" } },
+ "dav:name": "Calendar 2",
+ "apple:color": "#00FF00",
+ acl: [],
+ },
+ {
+ _links: { self: { href: "/calendars/u3/cal3.json" } },
+ "dav:name": "Calendar 3",
+ "apple:color": "#0000FF",
+ acl: [],
+ },
+ ];
+
+ (userAPI.getOpenPaasUser as jest.Mock).mockResolvedValue({ id: "u1" });
+ (calAPI.getCalendars as jest.Mock).mockResolvedValue({
+ _embedded: { "dav:calendar": mockCalendars },
+ });
+
+ const getUserDetailsMock = userAPI.getUserDetails as jest.Mock;
+ getUserDetailsMock
+ .mockResolvedValueOnce({
+ firstname: "Alice",
+ lastname: "Smith",
+ emails: ["alice@example.com"],
+ })
+ .mockResolvedValueOnce({
+ firstname: "Bob",
+ lastname: "Jones",
+ emails: ["bob@example.com"],
+ })
+ .mockResolvedValueOnce({
+ firstname: "Charlie",
+ lastname: "Brown",
+ emails: ["charlie@example.com"],
+ });
+
+ const store = storeFactory();
+ await store.dispatch(getCalendarsListAsync() as any);
+
+ expect(getUserDetailsMock).toHaveBeenCalledTimes(3);
+ expect(getUserDetailsMock).toHaveBeenCalledWith("u1");
+ expect(getUserDetailsMock).toHaveBeenCalledWith("u2");
+ expect(getUserDetailsMock).toHaveBeenCalledWith("u3");
+
+ const state = store.getState().calendars;
+ expect(state.list["u1/cal1"].owner).toContain("Alice");
+ expect(state.list["u2/cal2"].owner).toContain("Bob");
+ expect(state.list["u3/cal3"].owner).toContain("Charlie");
+ });
+
+ it("getCalendarsListAsync deduplicates getUserDetails calls for same ownerId", async () => {
+ const mockCalendars = [
+ {
+ _links: { self: { href: "/calendars/u1/cal1.json" } },
+ "dav:name": "Calendar 1",
+ "apple:color": "#FF0000",
+ acl: [],
+ },
+ {
+ _links: { self: { href: "/calendars/u1/cal2.json" } },
+ "dav:name": "Calendar 2",
+ "apple:color": "#00FF00",
+ acl: [],
+ },
+ {
+ _links: { self: { href: "/calendars/u1/cal3.json" } },
+ "dav:name": "Calendar 3",
+ "apple:color": "#0000FF",
+ acl: [],
+ },
+ ];
+
+ (userAPI.getOpenPaasUser as jest.Mock).mockResolvedValue({ id: "u1" });
+ (calAPI.getCalendars as jest.Mock).mockResolvedValue({
+ _embedded: { "dav:calendar": mockCalendars },
+ });
+
+ const getUserDetailsMock = userAPI.getUserDetails as jest.Mock;
+ getUserDetailsMock.mockResolvedValue({
+ firstname: "Alice",
+ lastname: "Smith",
+ emails: ["alice@example.com"],
+ });
+
+ const store = storeFactory();
+ await store.dispatch(getCalendarsListAsync() as any);
+
+ expect(getUserDetailsMock).toHaveBeenCalledTimes(1);
+ expect(getUserDetailsMock).toHaveBeenCalledWith("u1");
+ });
+
+ it("getCalendarsListAsync processes owners in batches of 20", async () => {
+ const mockCalendars = Array.from({ length: 45 }, (_, i) => ({
+ _links: { self: { href: `/calendars/u${i + 1}/cal${i + 1}.json` } },
+ "dav:name": `Calendar ${i + 1}`,
+ "apple:color": "#FF0000",
+ acl: [],
+ }));
+
+ (userAPI.getOpenPaasUser as jest.Mock).mockResolvedValue({ id: "u1" });
+ (calAPI.getCalendars as jest.Mock).mockResolvedValue({
+ _embedded: { "dav:calendar": mockCalendars },
+ });
+
+ const getUserDetailsMock = userAPI.getUserDetails as jest.Mock;
+ getUserDetailsMock.mockImplementation((ownerId: string) =>
+ Promise.resolve({
+ firstname: "User",
+ lastname: ownerId,
+ emails: [`${ownerId}@example.com`],
+ })
+ );
+
+ const store = storeFactory();
+ await store.dispatch(getCalendarsListAsync() as any);
+
+ expect(getUserDetailsMock).toHaveBeenCalledTimes(45);
+ const state = store.getState().calendars;
+ expect(Object.keys(state.list)).toHaveLength(45);
+ });
+
+ it("getCalendarsListAsync returns early if calendars already exist in store", async () => {
+ const existingCalendars = {
+ "u1/cal1": {
+ id: "u1/cal1",
+ name: "Existing Calendar",
+ events: {},
+ } as Calendars,
+ };
+
+ const store = storeFactory();
+ store.dispatch({
+ type: "calendars/getCalendars/fulfilled",
+ payload: { importedCalendars: existingCalendars, errors: "" },
+ });
+
+ const getUserDetailsMock = userAPI.getUserDetails as jest.Mock;
+ const getCalendarsMock = calAPI.getCalendars as jest.Mock;
+
+ await store.dispatch(getCalendarsListAsync() as any);
+
+ expect(getCalendarsMock).not.toHaveBeenCalled();
+ expect(getUserDetailsMock).not.toHaveBeenCalled();
+
+ const state = store.getState().calendars;
+ expect(state.list).toEqual(existingCalendars);
+ });
+
+ it("getCalendarsListAsync handles errors in getUserDetails gracefully", async () => {
+ const mockCalendars = [
+ {
+ _links: { self: { href: "/calendars/u1/cal1.json" } },
+ "dav:name": "Calendar 1",
+ "apple:color": "#FF0000",
+ acl: [],
+ },
+ {
+ _links: { self: { href: "/calendars/u2/cal2.json" } },
+ "dav:name": "Calendar 2",
+ "apple:color": "#00FF00",
+ acl: [],
+ },
+ ];
+
+ (userAPI.getOpenPaasUser as jest.Mock).mockResolvedValue({ id: "u1" });
+ (calAPI.getCalendars as jest.Mock).mockResolvedValue({
+ _embedded: { "dav:calendar": mockCalendars },
+ });
+
+ const getUserDetailsMock = userAPI.getUserDetails as jest.Mock;
+ getUserDetailsMock
+ .mockResolvedValueOnce({
+ firstname: "Alice",
+ lastname: "Smith",
+ emails: ["alice@example.com"],
+ })
+ .mockRejectedValueOnce(new Error("Failed to fetch user"));
+
+ const store = storeFactory();
+ const result = await store.dispatch(getCalendarsListAsync() as any);
+
+ expect(getUserDetailsMock).toHaveBeenCalledTimes(2);
+ const state = store.getState().calendars;
+ expect(state.list["u1/cal1"].owner).toContain("Alice");
+ expect(state.list["u2/cal2"].owner).toContain("Unknown User");
+ expect(result.payload.errors).toBeTruthy();
+ });
+
it("patchCalendarAsync.fulfilled updates calendar fields", () => {
const calId = "c1";
const prev = {
diff --git a/src/components/Calendar/Calendar.tsx b/src/components/Calendar/Calendar.tsx
index 7371914..5af7a8c 100644
--- a/src/components/Calendar/Calendar.tsx
+++ b/src/components/Calendar/Calendar.tsx
@@ -5,7 +5,7 @@ import interactionPlugin from "@fullcalendar/interaction";
import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
import "./Calendar.styl";
import "./CustomCalendar.styl";
-import { useEffect, useMemo, useRef, useState } from "react";
+import { MutableRefObject, useEffect, useMemo, useRef, useState } from "react";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import EventPopover from "../../features/Events/EventModal";
import { CalendarEvent } from "../../features/Events/EventsTypes";
@@ -44,7 +44,7 @@ import { updateDarkColor } from "./utils/calendarColorsUtils";
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
interface CalendarAppProps {
- calendarRef: React.RefObject;
+ calendarRef: MutableRefObject;
onDateChange?: (date: Date) => void;
onViewChange?: (view: string) => void;
}
@@ -65,13 +65,31 @@ export default function CalendarApp({
if (!tokens || !userId) {
dispatch(push("/"));
}
- }, [tokens, userId]);
+ }, [dispatch, tokens, userId]);
const calendars = useAppSelector((state) => state.calendars.list);
- const tempcalendars =
- useAppSelector((state) => state.calendars.templist) ?? {};
+ const tempcalendars = useAppSelector((state) => state.calendars.templist);
const [selectedCalendars, setSelectedCalendars] = useState([]);
+ const calendarLightSignature = useMemo(() => {
+ return Object.values(calendars || {})
+ .map((cal) => `${cal.id}:${cal.color?.light ?? ""}`)
+ .sort()
+ .join("|");
+ }, [calendars]);
+
+ const calendarIdsString = useMemo(
+ () =>
+ Object.keys(calendars || {})
+ .sort()
+ .join(","),
+ [calendars]
+ );
+ const calendarIds = useMemo(
+ () => (calendarIdsString ? calendarIdsString.split(",") : []),
+ [calendarIdsString]
+ );
+
const dottedEvents: CalendarEvent[] = selectedCalendars.flatMap((calId) => {
const calendar = calendars[calId];
if (!calendar?.events) return [];
@@ -100,65 +118,99 @@ export default function CalendarApp({
};
useEffect(() => {
- if (initialLoadRef.current && Object.keys(calendars || {}).length > 0 && userId) {
+ if (initialLoadRef.current && calendarIds.length > 0 && userId) {
const cached = localStorage.getItem("selectedCalendars");
if (cached && cached.length > 0) {
const parsed = JSON.parse(cached) as string[];
const valid = parsed.filter((id) => calendars[id]);
setSelectedCalendars(valid);
} else {
- const personalCalendarIds = Object.keys(calendars).filter(
+ const personalCalendarIds = calendarIds.filter(
(id) => id.split("/")[0] === userId
);
setSelectedCalendars(personalCalendarIds);
}
initialLoadRef.current = false;
}
- }, [calendars, userId]);
+ }, [calendarIds, calendars, userId]);
// Save selected cals to cache
useEffect(() => {
- if (Object.keys(calendars || {}).length > 0) {
+ if (calendarIds.length > 0) {
localStorage.setItem(
"selectedCalendars",
JSON.stringify(selectedCalendars)
);
}
- }, [selectedCalendars]);
+ }, [selectedCalendars, calendarIds.length]);
+
+ const prevCalendarLightSignature = useRef(null);
useEffect(() => {
+ if (!calendarLightSignature) {
+ prevCalendarLightSignature.current = calendarLightSignature;
+ return;
+ }
+
+ if (prevCalendarLightSignature.current === calendarLightSignature) {
+ return;
+ }
+
+ prevCalendarLightSignature.current = calendarLightSignature;
updateDarkColor(calendars || {}, theme, dispatch);
- }, [
- theme,
- Object.values(calendars || {})
- .map((c) => c.color?.dark)
- .join(","),
- ]);
+ }, [calendarLightSignature, calendars, theme, dispatch]);
useEffect(() => {
- const validCalendarIds = new Set(Object.keys(calendars || {}));
- setSelectedCalendars((prev) =>
- prev.filter((calId) => validCalendarIds.has(calId))
- );
- }, [calendars]);
+ if (calendarIds.length === 0) return;
+ const validCalendarIds = new Set(calendarIds);
+ setSelectedCalendars((prev) => {
+ const filtered = prev.filter((calId) => validCalendarIds.has(calId));
+ if (filtered.length === prev.length) {
+ const unchanged = filtered.every((id, index) => id === prev[index]);
+ if (unchanged) {
+ return prev;
+ }
+ }
+ return filtered;
+ });
+ }, [calendarIds]);
const calendarRange = useMemo(
() => getCalendarRange(selectedDate),
[selectedDate]
);
+ const calendarRangeStart = calendarRange.start.getTime();
+ const calendarRangeEnd = calendarRange.end.getTime();
+
+ const rangeStart = useMemo(
+ () => formatDateToYYYYMMDDTHHMMSS(new Date(calendarRangeStart)),
+ [calendarRangeStart]
+ );
+
+ const rangeEnd = useMemo(
+ () => formatDateToYYYYMMDDTHHMMSS(new Date(calendarRangeEnd)),
+ [calendarRangeEnd]
+ );
+
// Create a stable string key for the range
- const rangeKey = `${formatDateToYYYYMMDDTHHMMSS(
- calendarRange.start
- )}_${formatDateToYYYYMMDDTHHMMSS(calendarRange.end)}`;
+ const rangeKey = useMemo(
+ () => `${rangeStart}_${rangeEnd}`,
+ [rangeStart, rangeEnd]
+ );
let filteredEvents: CalendarEvent[] = extractEvents(
selectedCalendars,
calendars || {}
);
+ const tempCalendarIds = useMemo(
+ () => Object.keys(tempcalendars || {}).sort(),
+ [tempcalendars]
+ );
+
let filteredTempEvents: CalendarEvent[] = extractEvents(
- Object.keys(tempcalendars || {}),
+ tempCalendarIds,
tempcalendars || {}
);
@@ -169,55 +221,73 @@ export default function CalendarApp({
const prefetchedCalendarsRef = useRef>({});
const tempFetchedRangesRef = useRef>({});
+ const activeLoadCompletedRef = useRef(false);
+ const [activeLoadCompleted, setActiveLoadCompleted] =
+ useState(false);
useEffect(() => {
- if (!rangeKey || sortedSelectedCalendars.length === 0) return;
+ activeLoadCompletedRef.current = false;
+ setActiveLoadCompleted(false);
+ }, [rangeKey]);
+
+ useEffect(() => {
+ if (!rangeKey || sortedSelectedCalendars.length === 0) {
+ activeLoadCompletedRef.current = true;
+ setActiveLoadCompleted(true);
+ return;
+ }
let cancelled = false;
const ACTIVE_BATCH_SIZE = 5;
const loadCalendars = async () => {
- const pendingIds = sortedSelectedCalendars.filter(
- (id) => fetchedRangesRef.current[id] !== rangeKey
- );
+ try {
+ const pendingIds = sortedSelectedCalendars.filter(
+ (id) => fetchedRangesRef.current[id] !== rangeKey
+ );
- if (pendingIds.length === 0) {
- return;
- }
+ if (pendingIds.length === 0) {
+ activeLoadCompletedRef.current = true;
+ setActiveLoadCompleted(true);
+ return;
+ }
- const rangeStart = formatDateToYYYYMMDDTHHMMSS(calendarRange.start);
- const rangeEnd = formatDateToYYYYMMDDTHHMMSS(calendarRange.end);
+ for (
+ let i = 0;
+ i < pendingIds.length && !cancelled;
+ i += ACTIVE_BATCH_SIZE
+ ) {
+ const chunk = pendingIds.slice(i, i + ACTIVE_BATCH_SIZE);
- for (
- let i = 0;
- i < pendingIds.length && !cancelled;
- i += ACTIVE_BATCH_SIZE
- ) {
- const chunk = pendingIds.slice(i, i + ACTIVE_BATCH_SIZE);
+ chunk.forEach((id) => {
+ fetchedRangesRef.current[id] = rangeKey;
+ prefetchedCalendarsRef.current[id] = "active";
+ });
- chunk.forEach((id) => {
- fetchedRangesRef.current[id] = rangeKey;
- prefetchedCalendarsRef.current[id] = "active";
- });
+ const requests = chunk.map(async (id) => {
+ try {
+ await dispatch(
+ getCalendarDetailAsync({
+ calId: id,
+ match: {
+ start: rangeStart,
+ end: rangeEnd,
+ },
+ })
+ ).unwrap();
+ } catch (error) {
+ console.error(`Failed to load calendar ${id}:`, error);
+ fetchedRangesRef.current[id] = "";
+ }
+ });
- const requests = chunk.map(async (id) => {
- try {
- await dispatch(
- getCalendarDetailAsync({
- calId: id,
- match: {
- start: rangeStart,
- end: rangeEnd,
- },
- })
- ).unwrap();
- } catch (error) {
- console.error(`Failed to load calendar ${id}:`, error);
- fetchedRangesRef.current[id] = "";
- }
- });
-
- await Promise.all(requests);
+ await Promise.all(requests);
+ }
+ } finally {
+ if (!cancelled) {
+ activeLoadCompletedRef.current = true;
+ setActiveLoadCompleted(true);
+ }
}
};
@@ -226,22 +296,19 @@ export default function CalendarApp({
return () => {
cancelled = true;
};
- }, [
- dispatch,
- rangeKey,
- sortedSelectedCalendars,
- calendarRange,
- ]);
+ }, [dispatch, rangeKey, sortedSelectedCalendars, rangeStart, rangeEnd]);
useEffect(() => {
- if (!rangeKey) return;
+ if (!rangeKey || !activeLoadCompleted) return;
- const rangeStart = formatDateToYYYYMMDDTHHMMSS(calendarRange.start);
- const rangeEnd = formatDateToYYYYMMDDTHHMMSS(calendarRange.end);
-
- const hiddenCalendars = Object.keys(calendars || {})
+ const hiddenCalendars = calendarIds
.filter((id) => !selectedCalendars.includes(id))
- .filter((id) => prefetchedCalendarsRef.current[id] !== rangeKey);
+ .filter((id) => {
+ const prefetched = prefetchedCalendarsRef.current[id];
+ return prefetched !== rangeKey && prefetched !== "active";
+ });
+
+ if (hiddenCalendars.length === 0) return;
hiddenCalendars.forEach((id) => {
prefetchedCalendarsRef.current[id] = rangeKey;
@@ -261,43 +328,56 @@ export default function CalendarApp({
});
});
}, [
- calendars,
+ calendarIdsString,
selectedCalendars,
rangeKey,
- calendarRange,
dispatch,
+ rangeStart,
+ rangeEnd,
+ activeLoadCompleted,
]);
+ const calendarsWithClearedCache = useMemo(() => {
+ return selectedCalendars
+ .map((id) => {
+ const cleared = calendars[id]?.lastCacheCleared;
+ return cleared ? { id, cleared } : null;
+ })
+ .filter(Boolean) as { id: string; cleared: number }[];
+ }, [selectedCalendars, calendars]);
+
+ const processedCacheClearRef = useRef>({});
+
useEffect(() => {
- selectedCalendars.forEach((calId) => {
- const calendar = calendars[calId];
- if (calendar?.lastCacheCleared) {
- delete fetchedRangesRef.current[calId];
- prefetchedCalendarsRef.current[calId] = "";
-
- dispatch(
- getCalendarDetailAsync({
- calId,
- match: {
- start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start),
- end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end),
- },
- })
- );
-
- fetchedRangesRef.current[calId] = rangeKey;
- prefetchedCalendarsRef.current[calId] = rangeKey;
+ calendarsWithClearedCache.forEach(({ id, cleared }) => {
+ if (processedCacheClearRef.current[id] === cleared) {
+ return;
}
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [
- selectedCalendars.map((id) => calendars[id]?.lastCacheCleared).join(","),
- ]);
- const tempCalendarIds = useMemo(
- () => Object.keys(tempcalendars || {}).sort(),
- [tempcalendars]
- );
+ processedCacheClearRef.current[id] = cleared;
+ delete fetchedRangesRef.current[id];
+ prefetchedCalendarsRef.current[id] = "";
+
+ dispatch(
+ getCalendarDetailAsync({
+ calId: id,
+ match: {
+ start: rangeStart,
+ end: rangeEnd,
+ },
+ })
+ )
+ .unwrap()
+ .then(() => {
+ fetchedRangesRef.current[id] = rangeKey;
+ prefetchedCalendarsRef.current[id] = rangeKey;
+ })
+ .catch(() => {
+ fetchedRangesRef.current[id] = "";
+ prefetchedCalendarsRef.current[id] = "";
+ });
+ });
+ }, [calendarsWithClearedCache, dispatch, rangeKey, rangeStart, rangeEnd]);
const tempCalendarControllersRef = useRef