Our powerful JS Calendar component


Post by petr.grochal »

Hello. I am trying to implement Bryntum Calendar in our NextJS project. Everything seems to work fine except one thing. I am using "timeline" mode as shown in this example - https://bryntum.com/products/calendar/examples/calendar-scheduler/ .

Lets say the calendar lies on a route /calendar. If I want to navigate to another route, the calendar throws some runtime error which causes crash of the NextJS app.

I found out that this behaviour only happens when there is features prop (https://bryntum.com/products/calendar/docs/api/Scheduler/view/Scheduler#config-features) in timeline widget settings (event if it is an empty object). From the call stack I assume, that something is going wrong when the Scheduler instance in timeline widget is being destroyed.

Call Stack:

Uncaught TypeError: this.timeAxis[aYh(...)] is not a function
    at eval (calendar.module.js:983:367780)
    at Array.some (<anonymous>)
    at get hasVisibleEvents (calendar.module.js:983:367740)
    at Scheduler.refresh (calendar.module.js:983:386562)
    at ResourceTimeRanges.doDisable (calendar.module.js:983:1026009)
    at ResourceTimeRanges.updateDisabled (calendar.module.js:930:223498)
    at ResourceTimeRanges.set (calendar.module.js:930:55405)
    at ResourceTimeRanges.set (calendar.module.js:930:56183)
    at ResourceTimeRanges.setConfig (calendar.module.js:930:68305)
    at ResourceTimeRanges.configure (calendar.module.js:930:67242)
    at ResourceTimeRanges.construct (calendar.module.js:930:63612)
    at ResourceTimeRanges.construct (calendar.module.js:930:86604)
    at ResourceTimeRanges.construct (calendar.module.js:930:214585)
    at ResourceTimeRanges.construct (calendar.module.js:930:220182)
    at f.<computed>.construct (calendar.module.js:983:51137)
    at new _Base (calendar.module.js:930:63257)
    at new b (calendar.module.js:930:85718)
    at new b (calendar.module.js:930:213496)
    at new InstancePlugin (calendar.module.js:930:218630)
    at new b (calendar.module.js:983:825253)
    at new ResourceTimeRangesBase (calendar.module.js:983:1025296)
    at new ResourceTimeRanges (calendar.module.js:983:1283755)
    at Object.get (calendar.module.js:983:51241)
    at Object.values (<anonymous>)
    at Scheduler.doDestroy (calendar.module.js:983:159764)
    at Scheduler.doDestroy (calendar.module.js:983:348791)
    at Scheduler.doDestroy (calendar.module.js:983:318445)
    at Scheduler.doDestroy (calendar.module.js:983:364197)
    at Scheduler.doDestroy (calendar.module.js:983:825103)
    at Scheduler.doDestroy (calendar.module.js:983:774595)
    at Scheduler.doDestroy (calendar.module.js:983:805605)
    at Scheduler.doDestroy (calendar.module.js:930:286893)
    at Scheduler.doDestroy [as $oldDestructor] (calendar.module.js:983:807814)
    at Object.destructorInterceptor (calendar.module.js:930:93224)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval (calendar.module.js:930:43558)
    at Scheduler.eval [as doDestroy] (calendar.module.js:930:43558)
    at Scheduler.destroy (calendar.module.js:930:63956)

Any ideas how to prevent this runtime error?


Post by mats »

Hey there and welcome to the forums! Can you please provide us a small test case so we can reproduce this locally? Then we should be able to pinpoint the error quickly and unblock you.


Post by Animal »

Reading the stack trace, and trying to work out the app state from that, it could be that the destroy sequence is being initiated during instantiation.

It is attempting to iterate through the features object (Calling Object.values on it), destroying each property all to free up feature resources which is the correct thing to do during destruction.

But the features object initializes features on demand on first reference. Each feature gas a getter defined for it on the features object which instantiates the named feature for the Scheduler, then removes said getter leaving the feature ready for use.

It looks like that initial getter is being invoked during the destroy sequence meaning that the features have not all been initialized which happens on the tail end of the configuration process. At that stage it simply iterates the features object, referencing each property, triggering the getter which initializes each feature class.

From what code is destroy being called? My theory is that it is being called before it finishes configuration.


Post by petr.grochal »

Hello, thank you for your response and sorry for a late reply.

Here is small test case:

Stack
next: 14.2.23
react: 18.3.1
react-dom: 18.3.1
@bryntum/calendar: npm:@bryntum/calendar-trial@6.1.8
@bryntum/calendar-react: 6.1.8

Using npm 10.9.0 and node 22.12.0 for building

Using app router as a routing mechanism in Next.js

Lets say we have two routes in our demo app:

/calendar - the page with calendar itself
/test - some other page, for example Hello world page, it does not matter much

In layout we have some header menu with links to those two routes/pages

When I am on calendar page and click on the link to the "/test" page, that is the moment when the error occurs. Basically every time I am leaving the calendar page to another page.

To answer the qeustion when the destroy is called, I have to answer I do not know. It is called automatically. My guess is that it is called in some useEffect cleanup function in BryntumCalendar component (@bryntum/calendar-react). So it is not called in our code.

Our implementation

Calendar.tsx

'use client';

import dynamic from 'next/dynamic';

const CalendarCore = dynamic(() => import('./CalendarCore'), { ssr: false });

export const Calendar = () => {
	return <CalendarCore />;
};

CalendarCore.tsx

import { BryntumCalendar } from '@bryntum/calendar-react';
import { LocaleManager, LocaleHelper } from '@bryntum/calendar';
import '@bryntum/calendar/calendar.stockholm.css';
import localeCs from '@bryntum/calendar/locales/calendar.locale.Cs';
import { useRef, useState } from 'react';
import { useCalendarSettings } from './useCalendarSettings';
import { useParams, useSearchParams } from 'next/navigation';
import { useSession } from 'next-auth/react';
import { useQuery } from '@tanstack/react-query';
import { getEvents } from '@/api/fetchers/event';

const CalendarCore = () => {
	const { officeId } = useParams();
	const searchParams = useSearchParams();
	const startDate = searchParams.get('date');
	const session = useSession();
	const token = session.data?.accessToken?.token;
	const [date, setDate] = useState(startDate ? new Date(startDate) : new Date());
	const calendar = useRef<BryntumCalendar>(null);

LocaleHelper.publishLocale(localeCs);
LocaleManager.applyLocale(localeCs.localeName!);

const { data: events } = useQuery({
	queryKey: ['events', { id: officeId as string, date: date.toISOString(), token }],
	queryFn: getEvents,
	enabled: Boolean(token && officeId),
});

const { calendarProps } = useCalendarSettings({
	date: startDate ?? undefined,
	events: events?.items ?? [],
});

return (
	<BryntumCalendar ref={calendar} {...calendarProps} />
);
};

export default CalendarCore;

useCalendarSettings.ts

import { CalendarEventDTO } from '@/api/generated';
import {
	Calendar,
	DateHelper,
	DomConfig,
	EventModel,
	ResourceModel,
	Scheduler,
	SchedulerEventEdit,
	StringHelper,
} from '@bryntum/calendar';
import { BryntumCalendarProps } from '@bryntum/calendar-react';
import { useMemo } from 'react';

type Args = {
	events: CalendarEventDTO[];
	date?: string;
};

const tickWidth = 140;

export const useCalendarSettings = ({
	events,
	date,
}: Args) => {
	const calendarEvents = useMemo(
		() =>
			events.map((event) => ({
				...event,
				id: event.id!,
				resourceId: event.user,
				name: event.clientName,
			})),
		[events]
	);

const calendarProps: BryntumCalendarProps = useMemo(() => {
	return {
		date,
		mode: 'timeline' as const,
		modes: {
			timeline: {
				type: 'scheduler',

				// Used by the Calendar's mode selector button
				title: 'Timeline',

				// Used by resourceInfo column to base src for image field:
				// resourceImagePath: '../_shared/images/users/',

				// Change default event style for Scheduler to better match Calendars look
				eventStyle: 'calendar',

				// Calendar does not use initial animations, match that
				useInitialAnimation: false,

				columns: [{ type: 'resourceInfo', field: 'name', text: 'Uživatel', width: 175 }],

				features: {
					nonWorkingTime: true,
					timeRanges: {
						showCurrentTimeLine: true,
						showHeaderElements: true,
					},
					resourceTimeRanges: true,
				},

				workingTime: {
					fromHour: 6,
					toHour: 20,
				},

				barMargin: 3,
				viewPreset: {
					base: 'hourAndDay',
					tickWidth,

					headers: [
						{
							unit: 'day',
							dateFormat: 'ddd DD.MM.',
						},
						{
							unit: 'hour',
							dateFormat: 'HH:mm',
						},
					],
				},

				// Custom eventRenderer to match style used by Calendar
				eventRenderer({ eventRecord, renderData }) {
					if (eventRecord.isInterDay) {
						renderData.eventStyle = 'interday';
						return StringHelper.encodeHtml(eventRecord.name);
					}

					renderData.style = 'align-items: start';

					const { eventColor, iconCls } = renderData,
						noIcon = !(iconCls as string)?.length,
						isRecurring = eventRecord.isRecurring || eventRecord.isOccurrence;

					return {
						class: 'b-cal-event-body',
						children: [
							{
								class: 'b-event-header',
								children: [
									{
										class: 'b-event-time',
										text: DateHelper.format(new Date(eventRecord.startDate as string), 'LST'),
									},
									isRecurring && {
										tag: 'i',
										class: {
											'b-icon': 1,
											'b-fw-icon': 1,
											'b-cal-event-icon': !noIcon,
											'b-cal-recurrence-icon': noIcon,
											'b-icon-recurring': noIcon,
										},
										style: eventColor
											? {
													color: eventColor,
												}
											: null,
									},
								],
							},
							{
								class: 'b-cal-event-desc',
								text: eventRecord.name,
							},
						],
					} as DomConfig;
				},
			},
		},
		events: calendarEvents,
	};
}, [calendarEvents]);

return {
	calendarProps,
};
};

Post by marcio »

Hey petr.grochal,

Thanks for sharing.

I assembled a demo based on your description, but I don't see that error.

Could you please check it and see if I assembled everything as you described? If not, could you adapt it to reproduce the error, then send it back to us here?

Attachments
pages-router copy.zip
(4.2 MiB) Downloaded 9 times
Screen Recording 2025-04-29 at 17.39.50.mov
(4.55 MiB) Downloaded 13 times

Best regards,
Márcio

How to ask for help? Please read our Support Policy


Post by petr.grochal »

Hey Marcio. Thanks for quick reply. I will download your demo and check.

Will have vacation next few days. I should have some outcome on Monday.


Post by marcio »

Hey,

Thanks for the update, we'll be waiting for the feedback. Have a nice vacation!

Best regards,
Márcio

How to ask for help? Please read our Support Policy


Post by petr.grochal »

Hello Marcio, sorry for late reply. I checked your test app, updated it a little, but still could not get the error which was happening in our app. So I did more investigation to find the root cause of the problem.

It took some time, but I found the problem. It was somehow connected with a fact that we are using our own API to get the events and resources and then sending it as a props to the BryntumCalendar component.

So I started to investigate how else I can pass events and resources. In the end I am using BryntumCalendarProjectModel and passing that project into BryntumCalendar. This fixed the issue... no more runtime errors now.

Thank you all for your time.


Post by marcio »

Hey petr.grochal,

Thanks for the update, and glad that's all working now.

If you need any assistance, please don't hesitate to contact us here on the forums.

Best regards,
Márcio

How to ask for help? Please read our Support Policy


Post Reply