Summary
The Calendar premium lazy-loading plugin's store-effect selector returns a fresh object literal on every call, so — since registerStoreEffect compares with !== (reference equality) — the effect runs on every store update (selection, hover, drag placeholder moves…), not just when the visible days change. It's not a fetch storm (the effect guards on visibleDaysKey), but it wastes work on a hot path. The Timeline plugin does it correctly by returning a primitive string.
Details
x-scheduler-internals-premium/src/use-event-calendar-premium/plugins/EventCalendarPremiumLazyLoadingPlugin.ts:
store.registerStoreEffect(
(state) => {
const visibleDays = state.viewConfig?.visibleDaysSelector?.(state) ?? [];
const visibleDaysKey = visibleDays.map((day) => day.key).join('|');
return { viewConfig: state.viewConfig, visibleDaysKey, isLoading: state.isLoading }; // new object every call
},
(previous, next) => {
if (previous.visibleDaysKey === next.visibleDaysKey) return;
...
},
);
registerStoreEffect (SchedulerStore.ts):
if (nextValue !== previousValue) { effect(previousValue, nextValue); ... } // reference compare
A new object is never === the previous, so the effect runs on every notification. Per update it recomputes visibleDaysSelector(state) and rebuilds the joined key string — during a drag the store updates on every pointer move, so this churns each frame. (isLoading is included in the selected object but never used by the effect, adding extra re-runs.)
Compare the Timeline plugin, which returns a primitive and works correctly:
return `${state.adapter.getTime(viewConfig.start)}|${state.adapter.getTime(viewConfig.end)}`;
Suggested fix
Return a primitive key like the Timeline plugin — e.g. null when there's no viewConfig / not initialized, otherwise the visibleDaysKey string. The !== compare then only fires on real changes.
This also lets the instant-initial-load check use previousKey === null (as the Timeline does) instead of previous.viewConfig == null — which fixes the related issue where the Calendar's first lazy-load goes through the debounce instead of loading immediately. Worth fixing together.
Context
@mui/x-scheduler-internals-premium (master, pre-stable). Performance churn on a hot path; no correctness/fetch impact → Medium.
Summary
The Calendar premium lazy-loading plugin's store-effect selector returns a fresh object literal on every call, so — since
registerStoreEffectcompares with!==(reference equality) — the effect runs on every store update (selection, hover, drag placeholder moves…), not just when the visible days change. It's not a fetch storm (the effect guards onvisibleDaysKey), but it wastes work on a hot path. The Timeline plugin does it correctly by returning a primitive string.Details
x-scheduler-internals-premium/src/use-event-calendar-premium/plugins/EventCalendarPremiumLazyLoadingPlugin.ts:registerStoreEffect(SchedulerStore.ts):A new object is never
===the previous, so the effect runs on every notification. Per update it recomputesvisibleDaysSelector(state)and rebuilds the joined key string — during a drag the store updates on every pointer move, so this churns each frame. (isLoadingis included in the selected object but never used by the effect, adding extra re-runs.)Compare the Timeline plugin, which returns a primitive and works correctly:
Suggested fix
Return a primitive key like the Timeline plugin — e.g.
nullwhen there's noviewConfig/ not initialized, otherwise thevisibleDaysKeystring. The!==compare then only fires on real changes.This also lets the instant-initial-load check use
previousKey === null(as the Timeline does) instead ofprevious.viewConfig == null— which fixes the related issue where the Calendar's first lazy-load goes through the debounce instead of loading immediately. Worth fixing together.Context
@mui/x-scheduler-internals-premium(master, pre-stable). Performance churn on a hot path; no correctness/fetch impact → Medium.