Skip to content

Commit 3990cc5

Browse files
Use selectors instead of storing in state
1 parent d1feb05 commit 3990cc5

File tree

7 files changed

+87
-135
lines changed

7 files changed

+87
-135
lines changed

packages/x-scheduler-headless/src/scheduler-selectors/schedulerEventSelectors.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,40 @@
11
import { createSelector, createSelectorMemoized } from '@base-ui-components/utils/store';
2-
import { SchedulerEvent, CalendarEventId } from '../models';
2+
import { SchedulerEvent, CalendarEventId, SchedulerProcessedEvent } from '../models';
33
import { SchedulerState as State } from '../utils/SchedulerStore/SchedulerStore.types';
44
import { schedulerResourceSelectors } from './schedulerResourceSelectors';
5+
import { getProcessedEventFromModel } from '../utils/SchedulerStore/SchedulerStore.utils';
6+
7+
const processedEventsState = createSelectorMemoized(
8+
(state: State) => state.adapter,
9+
(state: State) => state.eventModelStructure,
10+
(state: State) => state.eventModelList,
11+
(adapter, eventModelStructure, eventModelList) => {
12+
const eventIdList: CalendarEventId[] = [];
13+
const eventModelLookup = new Map<CalendarEventId, any>();
14+
const processedEventLookup = new Map<CalendarEventId, SchedulerProcessedEvent>();
15+
const processedEventList: SchedulerProcessedEvent[] = [];
16+
17+
for (const event of eventModelList) {
18+
const processedEvent = getProcessedEventFromModel(event, adapter, eventModelStructure);
19+
eventIdList.push(processedEvent.id);
20+
eventModelLookup.set(processedEvent.id, event);
21+
processedEventLookup.set(processedEvent.id, processedEvent);
22+
processedEventList.push(processedEvent);
23+
}
24+
25+
return {
26+
idList: eventIdList,
27+
modelLookup: eventModelLookup,
28+
processedLookup: processedEventLookup,
29+
processedList: processedEventList,
30+
};
31+
},
32+
);
533

634
const processedEventSelector = createSelector(
7-
(state: State) => state.processedEventLookup,
8-
(processedEventLookup, eventId: CalendarEventId | null | undefined) =>
9-
eventId == null ? null : processedEventLookup.get(eventId),
35+
processedEventsState,
36+
(eventState, eventId: CalendarEventId | null | undefined) =>
37+
eventId == null ? null : eventState.processedLookup.get(eventId),
1038
);
1139

1240
const isEventReadOnlySelector = createSelector(
@@ -55,14 +83,13 @@ export const schedulerEventSelectors = {
5583
},
5684
),
5785
isMultiDayEvent: createSelector((state: State) => state.isMultiDayEvent),
58-
processedEventList: createSelectorMemoized(
59-
(state: State) => state.eventIdList,
60-
(state: State) => state.processedEventLookup,
61-
(eventIds, processedEventLookup) => eventIds.map((id) => processedEventLookup.get(id)!),
86+
processedEventList: createSelector(
87+
processedEventsState,
88+
(eventState) => eventState.processedList,
6289
),
63-
idList: createSelector((state: State) => state.eventIdList),
90+
idList: createSelector(processedEventsState, (eventState) => eventState.idList),
6491
modelList: createSelector((state: State) => state.eventModelList),
65-
modelLookup: createSelector((state: State) => state.eventModelLookup),
92+
modelLookup: createSelector(processedEventsState, (eventState) => eventState.modelLookup),
6693
canDragEventsFromTheOutside: createSelector(
6794
(state: State) => state.canDragEventsFromTheOutside && !state.readOnly,
6895
),

packages/x-scheduler-headless/src/scheduler-selectors/schedulerResourceSelectors.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,50 @@
11
import { createSelector, createSelectorMemoized } from '@base-ui-components/utils/store';
22
import { SchedulerState as State } from '../utils/SchedulerStore/SchedulerStore.types';
3+
import { CalendarResource, CalendarResourceId } from '../models';
4+
import { getProcessedResourceFromModel } from '../utils/SchedulerStore/SchedulerStore.utils';
5+
6+
const processedResourcesState = createSelectorMemoized(
7+
(state: State) => state.adapter,
8+
(state: State) => state.resourceModelStructure,
9+
(adapter, resourceModelStructure, resourceModelList) => {
10+
const resourceIdList: string[] = [];
11+
const processedResourceLookup = new Map<CalendarResourceId, CalendarResource>();
12+
const processedResourceList: CalendarResource[] = [];
13+
14+
for (const resource of resourceModelList) {
15+
const processedResource = getProcessedResourceFromModel(resource, resourceModelStructure);
16+
resourceIdList.push(processedResource.id);
17+
processedResourceLookup.set(processedResource.id, processedResource);
18+
processedResourceList.push(processedResource);
19+
}
20+
21+
return {
22+
idList: resourceIdList,
23+
processedLookup: processedResourceLookup,
24+
processedList: processedResourceList,
25+
};
26+
},
27+
);
328

429
const processedResourceSelector = createSelector(
5-
(state: State) => state.processedResourceLookup,
6-
(processedResourceLookup, resourceId: string | null | undefined) =>
7-
resourceId == null ? null : processedResourceLookup.get(resourceId),
30+
processedResourcesState,
31+
(resourceState, resourceId: string | null | undefined) =>
32+
resourceId == null ? null : resourceState.processedLookup.get(resourceId),
833
);
934

1035
export const schedulerResourceSelectors = {
1136
processedResource: processedResourceSelector,
12-
processedResourceList: createSelectorMemoized(
13-
(state: State) => state.resourceIdList,
14-
(state: State) => state.processedResourceLookup,
15-
(resourceIds, processedResourceLookup) =>
16-
resourceIds.map((id) => processedResourceLookup.get(id)!),
37+
processedResourceList: createSelector(
38+
processedResourcesState,
39+
(resourceState) => resourceState.processedList,
1740
),
18-
idList: createSelector((state: State) => state.resourceIdList),
41+
idList: createSelector(processedResourcesState, (resourceState) => resourceState.idList),
1942
visibleMap: createSelector((state: State) => state.visibleResources),
2043
visibleIdList: createSelectorMemoized(
21-
(state: State) => state.resourceIdList,
44+
processedResourcesState,
2245
(state: State) => state.visibleResources,
23-
(resources, visibleResources) =>
24-
resources
46+
(resourceState, visibleResources) =>
47+
resourceState.idList
2548
.filter(
2649
(resourceId) =>
2750
!visibleResources.has(resourceId) || visibleResources.get(resourceId) === true,

packages/x-scheduler-headless/src/use-event-calendar/tests/core.EventCalendarStore.test.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
DEFAULT_VIEWS,
88
EventCalendarStore,
99
} from '../EventCalendarStore';
10+
import { EventCalendarState } from '../EventCalendarStore.types';
1011
import { CalendarView } from '../../models';
1112
import { DEFAULT_IS_MULTI_DAY_EVENT } from '../../utils/SchedulerStore';
1213

@@ -19,17 +20,13 @@ describe('Core - EventCalendarStore', () => {
1920
it('should initialize default state', () => {
2021
const store = new EventCalendarStore(DEFAULT_PARAMS, adapter);
2122

22-
const expectedState = {
23+
const expectedState: EventCalendarState = {
2324
adapter,
2425
view: DEFAULT_VIEW,
2526
views: DEFAULT_VIEWS,
26-
eventIdList: [],
2727
eventModelList: [],
28-
eventModelLookup: new Map(),
29-
processedEventLookup: new Map(),
28+
resourceModelList: [],
3029
eventModelStructure: undefined,
31-
resourceIdList: [],
32-
processedResourceLookup: new Map(),
3330
resourceModelStructure: undefined,
3431
visibleResources: new Map(),
3532
nowUpdatedEveryMinute: adapter.date(),

packages/x-scheduler-headless/src/use-timeline/tests/core.TimelineStore.test.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { adapter } from 'test/utils/scheduler';
22
import { createRenderer } from '@mui/internal-test-utils/createRenderer';
33
import { DEFAULT_IS_MULTI_DAY_EVENT } from '../../utils/SchedulerStore';
44
import { DEFAULT_PREFERENCES, TimelineStore } from '../TimelineStore';
5+
import { TimelineState } from '../TimelineStore.types';
56

67
const DEFAULT_PARAMS = { events: [] };
78

@@ -12,16 +13,12 @@ describe('Core - TimelineStore', () => {
1213
it('should initialize default state', () => {
1314
const store = new TimelineStore(DEFAULT_PARAMS, adapter);
1415

15-
const expectedState = {
16+
const expectedState: TimelineState = {
1617
adapter,
1718
visibleResources: new Map(),
18-
eventIdList: [],
1919
eventModelList: [],
20-
eventModelLookup: new Map(),
21-
processedEventLookup: new Map(),
20+
resourceModelList: [],
2221
eventModelStructure: undefined,
23-
resourceIdList: [],
24-
processedResourceLookup: new Map(),
2522
resourceModelStructure: undefined,
2623
nowUpdatedEveryMinute: adapter.date(),
2724
isMultiDayEvent: DEFAULT_IS_MULTI_DAY_EVENT,

packages/x-scheduler-headless/src/utils/SchedulerStore/SchedulerStore.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Store } from '@base-ui-components/utils/store';
22
// TODO: Use the Base UI warning utility once it supports cleanup in tests.
33
import { warnOnce } from '@mui/x-internals/warning';
4+
import { EMPTY_ARRAY, EMPTY_OBJECT } from '@base-ui-components/utils/empty';
45
import {
56
SchedulerProcessedEvent,
67
CalendarEventId,
@@ -29,8 +30,6 @@ import {
2930
} from '../recurring-event-utils';
3031
import { schedulerEventSelectors } from '../../scheduler-selectors';
3132
import {
32-
buildEventsState,
33-
buildResourcesState,
3433
createEventModel,
3534
getUpdatedEventModelFromChanges,
3635
shouldUpdateOccurrencePlaceholder,
@@ -80,10 +79,8 @@ export class SchedulerStore<
8079
instanceName: string,
8180
mapper: SchedulerParametersToStateMapper<State, Parameters>,
8281
) {
83-
const schedulerInitialState: SchedulerState<TEvent> = {
82+
const schedulerInitialState: SchedulerState<TEvent, TResource> = {
8483
...SchedulerStore.deriveStateFromParameters(parameters, adapter),
85-
...buildEventsState(parameters, adapter),
86-
...buildResourcesState(parameters),
8784
preferences: DEFAULT_SCHEDULER_PREFERENCES,
8885
adapter,
8986
occurrencePlaceholder: null,
@@ -130,6 +127,10 @@ export class SchedulerStore<
130127
) {
131128
return {
132129
adapter,
130+
eventModelList: parameters.events,
131+
eventModelStructure: parameters.eventModelStructure ?? EMPTY_OBJECT,
132+
resourceModelList: parameters.resources ?? EMPTY_ARRAY,
133+
resourceModelStructure: parameters.resourceModelStructure ?? EMPTY_OBJECT,
133134
areEventsDraggable: parameters.areEventsDraggable ?? false,
134135
areEventsResizable: parameters.areEventsResizable ?? false,
135136
canDragEventsFromTheOutside: parameters.canDragEventsFromTheOutside ?? false,
@@ -183,21 +184,6 @@ export class SchedulerStore<
183184
adapter,
184185
) as Partial<State>;
185186

186-
if (
187-
parameters.events !== this.parameters.events ||
188-
parameters.eventModelStructure !== this.parameters.eventModelStructure ||
189-
adapter !== this.state.adapter
190-
) {
191-
Object.assign(newSchedulerState, buildEventsState(parameters, adapter));
192-
}
193-
194-
if (
195-
parameters.resources !== this.parameters.resources ||
196-
parameters.resourceModelStructure !== this.parameters.resourceModelStructure
197-
) {
198-
Object.assign(newSchedulerState, buildResourcesState(parameters));
199-
}
200-
201187
updateModel(newSchedulerState, 'visibleDate', 'defaultVisibleDate');
202188

203189
const newState = this.mapper.updateStateFromParameters(

packages/x-scheduler-headless/src/utils/SchedulerStore/SchedulerStore.types.ts

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
CalendarEventColor,
44
CalendarEventOccurrence,
55
CalendarOccurrencePlaceholder,
6-
CalendarResource,
76
CalendarResourceId,
87
CalendarEventUpdatedProperties,
98
SchedulerValidDate,
@@ -15,7 +14,7 @@ import {
1514
} from '../../models';
1615
import { Adapter } from '../../use-adapter/useAdapter.types';
1716

18-
export interface SchedulerState<TEvent extends object = any> {
17+
export interface SchedulerState<TEvent extends object = any, TResource extends object = any> {
1918
/**
2019
* The adapter of the date library.
2120
* Not publicly exposed, is only set in state to avoid passing it to the selectors.
@@ -29,32 +28,16 @@ export interface SchedulerState<TEvent extends object = any> {
2928
* The model of the events available in the calendar as provided to props.events.
3029
*/
3130
eventModelList: readonly TEvent[];
32-
/**
33-
* The IDs of the events available in the calendar.
34-
*/
35-
eventIdList: CalendarEventId[];
36-
/**
37-
* A lookup to get the event model as provided to props.events from its ID.
38-
*/
39-
eventModelLookup: Map<CalendarEventId, TEvent>;
40-
/**
41-
* A lookup to get the processed event from its ID.
42-
*/
43-
processedEventLookup: Map<CalendarEventId, SchedulerProcessedEvent>;
4431
/**
4532
* The structure of the event model.
4633
* It defines how to read and write properties of the event model.
4734
* If not provided, the event model is assumed to match the `CalendarEvent` interface.
4835
*/
4936
eventModelStructure: SchedulerEventModelStructure<any> | undefined;
5037
/**
51-
* The IDs of the resources the events can be assigned to.
52-
*/
53-
resourceIdList: readonly CalendarResourceId[];
54-
/**
55-
* A lookup to get the processed resource from its ID.
38+
* The model of the resources available in the calendar as provided to props.resources.
5639
*/
57-
processedResourceLookup: Map<CalendarResourceId, CalendarResource>;
40+
resourceModelList: readonly TResource[];
5841
/**
5942
* The structure of the resource model.
6043
* It defines how to read properties of the resource model.

packages/x-scheduler-headless/src/utils/SchedulerStore/SchedulerStore.utils.ts

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
import { EMPTY_ARRAY } from '@base-ui-components/utils/empty';
21
import {
32
SchedulerProcessedEvent,
4-
CalendarEventId,
53
CalendarOccurrencePlaceholder,
64
CalendarResource,
7-
CalendarResourceId,
85
SchedulerEventModelStructure,
96
SchedulerResourceModelStructure,
107
SchedulerEvent,
118
} from '../../models';
129
import { processEvent } from '../../process-event';
1310
import { Adapter } from '../../use-adapter/useAdapter.types';
14-
import { SchedulerParameters, SchedulerState } from './SchedulerStore.types';
1511

1612
/**
1713
* Determines if the occurrence placeholder has changed in a meaningful way that requires updating the store.
@@ -41,8 +37,6 @@ export function shouldUpdateOccurrencePlaceholder(
4137
return false;
4238
}
4339

44-
export const DEFAULT_EVENT_MODEL_STRUCTURE: SchedulerEventModelStructure<any> = {};
45-
4640
const EVENT_PROPERTIES_LOOKUP: { [P in keyof SchedulerEvent]-?: true } = {
4741
id: true,
4842
title: true,
@@ -174,58 +168,3 @@ type AnyEventSetter<TEvent extends object> = (
174168
event: TEvent | Partial<TEvent>,
175169
value: any,
176170
) => TEvent;
177-
178-
export function buildEventsState<TEvent extends object, TResource extends object>(
179-
parameters: Pick<SchedulerParameters<TEvent, TResource>, 'events' | 'eventModelStructure'>,
180-
adapter: Adapter,
181-
): Pick<
182-
SchedulerState<TEvent>,
183-
| 'eventIdList'
184-
| 'eventModelLookup'
185-
| 'processedEventLookup'
186-
| 'eventModelStructure'
187-
| 'eventModelList'
188-
> {
189-
const { events, eventModelStructure } = parameters;
190-
191-
const eventIdList: CalendarEventId[] = [];
192-
const eventModelLookup = new Map<CalendarEventId, TEvent>();
193-
const processedEventLookup = new Map<CalendarEventId, SchedulerProcessedEvent>();
194-
for (const event of events) {
195-
const processedEvent = getProcessedEventFromModel(event, adapter, eventModelStructure);
196-
eventIdList.push(processedEvent.id);
197-
eventModelLookup.set(processedEvent.id, event);
198-
processedEventLookup.set(processedEvent.id, processedEvent);
199-
}
200-
201-
return {
202-
eventIdList,
203-
eventModelLookup,
204-
eventModelStructure,
205-
processedEventLookup,
206-
eventModelList: events,
207-
};
208-
}
209-
210-
export function buildResourcesState<TEvent extends object, TResource extends object>(
211-
parameters: Pick<SchedulerParameters<TEvent, TResource>, 'resources' | 'resourceModelStructure'>,
212-
): Pick<
213-
SchedulerState<TEvent>,
214-
'resourceIdList' | 'processedResourceLookup' | 'resourceModelStructure'
215-
> {
216-
const { resources = EMPTY_ARRAY, resourceModelStructure } = parameters;
217-
218-
const resourceIdList: string[] = [];
219-
const processedResourceLookup = new Map<CalendarResourceId, CalendarResource>();
220-
for (const resource of resources) {
221-
const processedResource = getProcessedResourceFromModel(resource, resourceModelStructure);
222-
resourceIdList.push(processedResource.id);
223-
processedResourceLookup.set(processedResource.id, processedResource);
224-
}
225-
226-
return {
227-
resourceIdList,
228-
processedResourceLookup,
229-
resourceModelStructure,
230-
};
231-
}

0 commit comments

Comments
 (0)