Use Case
I am using BryntumSchedulerPro in a React component to display a Gantt chart. The component is initialized with data (events, resources, dependencies, etc.), and these data sets can be loaded at different times. This part works fine.
However, I also need the component to support updates programatically. Such as:
- Adding or removing events.
- Filtering or editing events or resources.
- Expanding or collapsing resources programmatically (e.g., via external buttons).
- To handle updates, I am using store.applyChangeset to calculate and apply the necessary changes to the stores. While this works for some cases, it does not work consistently for others.
Problem Description
- Hiding/Removing Events: When I remove an event (e.g., filtering it out) and later make it visible again, the event is correctly added back to the eventStore (verified via store.records), but it does not appear in the UI.
- UI Not Updating: Even after calling project.acceptChanges() and project.commitAsync(), the UI does not reflect the changes.
- Inconsistent Behavior: Removing events works fine (the UI updates correctly), but adding them back does not.
import React, { useRef, useEffect } from 'react';
import { BryntumSchedulerPro, BryntumSchedulerProProps, BryntumSchedulerProProjectModel } from '@bryntum/scheduler-react';
import {
PresetManager,
ViewPreset,
ProjectModel,
CalendarManagerStore,
ResourceTimeRangeStore,
DependencyStore,
AssignmentStore,
EventStore,
ResourceStore,
TimeRangeStore,
} from '@bryntum/schedulerpro';
// Function to calculate differences between two objects
const getDifferences = (obj1, obj2) => {
const diff = {};
for (const key in obj1) {
if (obj1[key] !== obj2[key] && obj2[key] != null) {
diff[key] = obj2[key];
}
}
return diff;
};
// Reusable function to apply changes to a store
const applyStoreChanges = (store, newData, project) => {
const currentData = store.records?.map((record) => record.data) ?? [];
// Find records to add
const recordsToAdd = newData.filter((newRecord) => {
return !currentData.some((record) => record.id === newRecord.id);
});
// Find records to update
const recordsToUpdate = newData
.filter((newRecord) => {
const existingRecord = currentData.find((record) => record.id === newRecord.id);
return existingRecord && JSON.stringify(existingRecord) !== JSON.stringify(newRecord);
})
.map((newRecord) => {
const existingRecord = currentData.find((record) => record.id === newRecord.id);
return { ...getDifferences(existingRecord, newRecord), id: newRecord.id };
});
// Find records to remove
const recordsToRemove = currentData.filter(
(record) => !newData.some((newRecord) => newRecord.id === record.id)
);
project.acceptChanges()
// Apply changes to the store
store.applyChangeset({
added: recordsToAdd,
updated: recordsToUpdate,
removed: recordsToRemove,
});
project.commitAsync();
};
const GanttChart: React.FunctionComponent<Props> = (props) => {
const schedulerProRef = useRef<BryntumSchedulerPro>(null);
const projectRef = useRef<BryntumSchedulerProProjectModel>({resources: [], events: [], assignments: [], dependencies: [], calendars: [], resourceTimeRanges: []});
const getSchedulerConfig = (): BryntumSchedulerProProps => {
const { xAxis, yAxis, bryntumConfig, eventStyle, eventLayout, timelineZoomable } = props;
return {
autoHeight: !bryntumConfig?.height,
...
columns: [
{
type: 'tree',
field: 'name',
text: yAxis?.label,
showImage: false,
width: 200,
},
],
};
};
const stringCalendar = JSON.stringify(props.calendars);
const stringEvents = JSON.stringify(props.events);
const stringResources = JSON.stringify(props.resources);
const stringResourceTimeRanges = JSON.stringify(props.resourceTimeRanges);
const stringAssignments = JSON.stringify(props.assignments);
const stringDependencies = JSON.stringify(props.dependencies);
const stringTimeRanges = JSON.stringify(props.timeRanges);
useEffect(() => {
if (projectRef.current) {
applyStoreChanges(projectRef.current.instance.assignmentStore, props.assignments || [], projectRef.current.instance);
}
}, [stringAssignments]);
useEffect(() => {
if (projectRef.current) {
applyStoreChanges(projectRef.current.instance.dependencyStore, props.dependencies || [], projectRef.current.instance);
}
}, [stringDependencies]);
useEffect(() => {
if (projectRef.current) {
applyStoreChanges(projectRef.current.instance.timeRangeStore, props.timeRanges || [], projectRef.current.instance);
}
}, [stringTimeRanges]);
useEffect(() => {
if (projectRef.current) {
applyStoreChanges(projectRef.current.instance.calendarManagerStore, props.calendars || [], projectRef.current.instance);
}
}, [stringCalendar]);
useEffect(() => {
if (projectRef.current) {
applyStoreChanges(projectRef.current.instance.resourceTimeRangeStore, props.resourceTimeRanges || [], projectRef.current.instance);
}
}, [stringResourceTimeRanges]);
useEffect(() => {
if (projectRef.current) {
applyStoreChanges(projectRef.current.instance.resourceStore, props.resources || [], projectRef.current.instance);
}
}, [stringResources]);
useEffect(() => {
if (projectRef.current) {
applyStoreChanges(projectRef.current.instance.eventStore, props.events || [], projectRef.current.instance);
}
}, [stringEvents]);
const schedulerConfig = getSchedulerConfig();
React.useEffect(() => {
if (schedulerProRef.current) {
const start = new Date(props.xAxis?.startTime);
const end = new Date(props.xAxis?.endTime);
if (!isNaN(start?.getTime()) && !isNaN(end?.getTime())) {
schedulerProRef.current.instance.setTimeSpan(start, end);
}
}
}, [props.xAxis?.startTime, props.xAxis?.endTime]);
return (
<div className={`c3-gantt-chart bryntum-theme-${bryntumTheme}`}>
{props.header}
<div className="c3-scheduler">
<BryntumSchedulerProProjectModel
ref={projectRef}
/>
<BryntumSchedulerPro
{...schedulerConfig}
ref={schedulerProRef}
project={projectRef}
// more props for events callbacks
/>
</div>
{props.footer}
</div>
);
};
export default GanttChart;
Bryntum SchedulerPro Version: 5.6.8