when EventCalendar is inside a tab, error when selecting a different tab, onUnmount.
Using react-bootstrap tabs, and functional react with typescript.
mobiscroll.react.min.js:2 Uncaught TypeError: Cannot read properties of undefined (reading 'unsubscribe')
at t._destroy (mobiscroll.react.min.js:2:1)
at t.componentWillUnmount (mobiscroll.react.min.js:2:1)
at eval (eval at callComponentWillUnmountWithTimer (vendors-02d1408ccd4d994dcf08.js:1:1), <anonymous>:1:10)
at callComponentWillUnmountWithTimer (react-dom.development.js:19577:1)
at HTMLUnknownElement.callCallback (react-dom.development.js:188:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:237:1)
at invokeGuardedCallback (react-dom.development.js:292:1)
at safelyCallComponentWillUnmount (react-dom.development.js:19587:1)
at commitUnmount (react-dom.development.js:20109:1)
at commitNestedUnmounts (react-dom.development.js:20163:1)
gabi
March 2, 2022, 2:11pm
2
Hello Kate,
Could you please share your relevant code so we can reproduce this?
Yep. I’ve pinpointed the issue to 1. when toggling between tabs, the issue is with the api call/promise. It does not error out when using your getJson example. 2. The same error occurs when you try to toggle the calendar view with getJson as well as the api call.
const App = () => (
<Tabs unmountOnExit id="library-tabs" defaultActiveKey={"other"}>
<Tab key="other" eventKey="other" title="Other">
<div>Other Stuff</div>
</Tab>
<Tab key="calendar" eventKey="calendar" title="Calendar">
<TasksCalendar />
</Tab>
</Tabs>
);
const TasksCalendar = () => {
const [events, setEvents] = useState<Array<MbscCalendarEvent>>([]);
const onPageLoading = (event, inst) => {
service.getTimeline({
caseId,
params: {
start_date: moment(event.firstDay).format("YYYY-MM-DD"),
end_date: moment(event.lastDay).format("YYYY-MM-DD"),
},
}).then((events) => {
setEvents(events);
});
};
return (
<div>
<EventCalendar
data={events}
onPageLoading={onPageLoading}
popUpTitle1={gettext("Status")}
popUpTitle2={gettext("Due On")}
/>
</div>
);
};
export default TasksCalendar;
const EventCalendar = (props) => {
const { width, height } = useWindowSize();
const [view, setView] = useState("month");
const [largeView, setLargeView] = useState(false);
const [isOpen, setOpen] = useState(false);
const [event, setEvent] = useState<MbscCalendarEvent | undefined>();
const [anchor, setAnchor] = React.useState();
const [calView, setCalView] = useState<MbscEventcalendarView>({
calendar: {
labels: true,
type: "month",
},
});
setOptions({
theme: "ios",
themeVariant: "light",
});
useEffect(() => {
setLargeView(width > 600);
if (width <= 600) {
setCalView({
calendar: { type: "month" },
agenda: { type: "month" },
});
}
}, [width]);
const onEventClick = (e) => {
setOpen(e.event.id !== event?.id ? true : !isOpen);
setAnchor(e.domEvent.target);
setEvent(e.event);
};
const changeView = (e) => {
let labels = largeView;
let view = {};
switch (e.target.value) {
case "month":
view = {
calendar: { labels, type: "month" },
};
if (!labels) {
view["agenda"] = { type: "month" };
}
break;
case "week":
view = {
calendar: { labels, type: "week" },
agenda: { type: "week" },
};
break;
case "day":
view = {
agenda: { type: "day" },
};
break;
}
setView(e.target.value);
setCalView(view);
};
const customWithNavButtons = () => {
return (
<>
<CalendarNav className="cal-header-nav mr-auto" />
<CalendarPrev className="cal-header-prev align-self-center" />
{largeView && <CalendarToday className="md-custom-header-today" />}
<CalendarNext className="cal-header-next align-self-center" />
<div className="cal-header-picker ml-auto">
<SegmentedGroup themeVariant="light" value={view} onChange={changeView}>
<SegmentedItem value="month">{largeView ? "Month" : "M"}</SegmentedItem>
<SegmentedItem value="week">{largeView ? "Week" : "W"}</SegmentedItem>
<SegmentedItem value="day">{largeView ? "Day" : "D"}</SegmentedItem>
</SegmentedGroup>
</div>
</>
);
};
const renderLabel = (data) => {
if (data.isMultiDay) {
return (
<div style={{ color: "#000" }} className={`${data.original.className} pl-1 multi-day-event fs-16`}>
{" "}
{data.original.title}
</div>
);
} else {
return (
<>
<div className="single-day-event-dot" style={{ background: data.original.color }}></div>
<div style={{ color: "#000" }} className={`${data.original.className} pl-1 single-day-event fs-16`}>
{data.original.title}
</div>
</>
);
}
};
return (
<div className="md-switching-view-cont">
<Eventcalendar
theme="ios"
themeVariant="light"
renderHeader={customWithNavButtons}
view={calView}
cssClass="md-custom-header"
renderLabel={renderLabel}
onEventClick={onEventClick}
{...props}
/>
<Popup
display="anchored"
isOpen={isOpen}
anchor={anchor}
touchUi={false}
showOverlay={false}
contentPadding={false}
closeOnOverlayClick={true}
closeOnEsc={true}
width={350}
onClose={() => setOpen(false)}
cssClass="md-tooltip"
>
<div>
<div className={`px-2 md-tooltip-header py-1 ${event?.className}`}>
<span className="md-tooltip-title fs-20 font-weight-semi-bold">{event?.title}</span>
</div>
<div className="pt-1 px-2 md-tooltip-info mb-2">
<div className="md-tooltip-title fs-14 pb-1">
<div>{props.popUpTitle1 || ""}</div>
<div className="pt-1">{event?.statusBadge}</div>
</div>
<hr />
<div className="md-tooltip-title fs-14 pt-1">
<div>{props.popUpTitle2 || ""}</div>
<div className="pt-1 pb-2">
{event?.end && moment.isMoment(event.end) ? event.end.format("MM-DD-YYYY") : null}
</div>
</div>
{event?.button}
</div>
</div>
</Popup>
</div>
);
};
export default EventCalendar;
We are seing this too, I think it is related to react strict mode (and now always react 18). It is calling willUnmount twice and in the second run something is not there anymore
It is here: /core/components/eventcalendar/scheduler/schedule-event.ts in this code:
protected _destroy() {
if (this._el) {
this._el.blur();
}
if (this._unsubscribe) {
const id = this.s.event.uid!;
const observable = stateObservables[id];
observable.unsubscribe(this._unsubscribe);
if (!observable.nr) {
delete stateObservables[id];
}
}
if (this._unlisten) {
this._unlisten();
}
unlisten(this._doc, TOUCH_START, this._onDocTouch);
unlisten(this._doc, MOUSE_DOWN, this._onDocTouch);
}
Wrapping observable.unsubscr… in an if (observable) { solves it
Zoli
April 5, 2022, 9:04am
6
Hi folks!
Unfortunately I still could not reproduce this with Mobiscroll Version 5.15.2 and react 18. Can any of you put together a simple project where this can be reproduced?
Thanks,
Zoli
Make sure you are using the new createRoot function:
const root = createRoot(document.getElementById(‘mainMountPoint’));
root.render();
Else it will not run in concurrent mode (where it can fire willUnmount twice etc)
It is cleaning out my jsx, but as argument to root.render there should be a react component
Zoli
April 12, 2022, 4:50pm
8
@David_Erenger I did try with createRoot also with strict mode like this (with no luck)
const container = document.getElementById('root') as HTMLElement;
const root = ReactDOMClient.createRoot(container);
root.render(<React.StrictMode>
<App />
</React.StrictMode>);
With the release of React 18.1.0 we got the explanation to this:
Fix componentWillUnmount
firing twice inside of Suspense
We use suspense for all content and I suspect Kate_Bultman does too?