Our pure JavaScript Scheduler component


Post by mats »

Just dummy data should do hopefully (similar to how our demos are structured). Without being able to run code, it's hard for us to assist.


Post by jasony »

I managed to scale down our class in order to showcase the problem. It seems that whenever it re-renders the whole UI breaks down.

The class is as follows:

/* eslint-disable */

import React, { Component } from "react";
import classNames from "classnames";
import styled from "styled-components";
import { diff } from "deep-object-diff";
import "components/common/Scheduler/Scheduler.styles.scss";
import { zoomLevelMapping } from "config/zoomConfig";
import { BryntumScheduler } from "@bryntum/scheduler-react";

const SchedulerContainer = styled.div`
  grid-column: 1 / 2;
  grid-row: 2;
  overflow: hidden;
  padding: 10px 0 0 0;
`;

export default class OldBryntumScheduler extends Component {
  constructor(props) {
    super(props);
    this.schedulerRef = React.createRef();
  }
  componentDidMount() {
    const { onBryntumInitialized, zoomLevel, startDate, endDate } = this.props;

this.schedulerInstance = this.schedulerRef.current?.instance;

onBryntumInitialized(this.schedulerInstance);

this.resetScheduleTimeRange({ centerDate: new Date(), zoomLevel, startDate, endDate });
  }

  shouldComponentUpdate(nextProps) {
    const updatedProps = Object.keys(diff(nextProps, this.props));

if (updatedProps.includes("events")) return true;
if (updatedProps.includes("shiftInstances")) return true;

return false;
  }

  resetScheduleTimeRange({ centerDate, zoomLevel, startDate, endDate }) {
    this.schedulerInstance.zoomTo({
      ...zoomLevelMapping(zoomLevel),
      startDate,
      centerDate: centerDate || this.schedulerInstance.viewportCenterDate,
      endDate,
    });
  }

  render() {
    return (
      <SchedulerContainer className={classNames("spec-schedule", "b-react-scheduler-container")} id="bryntum-scheduler">
        <BryntumScheduler
          ref={this.schedulerRef}
          {...this.props.config}
          events={this.props.events}
          timeRanges={this.props.shiftInstances}
        />
      </SchedulerContainer>
    );
  }
}

I am working on trying to adapt a demo to see if I can reproduce but maybe something is obvious in the above code? If componentShouldUpdate returns false, any edits I make work but additions and removals do not work.

I also attempted to use hooks instead of sending the data in. In this code sample it rendered but the dates and times just flashed once and then disappeared. The first edit i made to an event was fine, the second one broke the entire UI again


import React, { useEffect, useRef } from "react";
import classNames from "classnames";
import styled from "styled-components";
import "components/common/Scheduler/Scheduler.styles.scss";
import { zoomLevelMapping } from "config/zoomConfig";
import { BryntumScheduler } from "@bryntum/scheduler-react";

const SchedulerContainer = styled.div`
  grid-column: 1 / 2;
  grid-row: 2;
  overflow: hidden;
  padding: 10px 0 0 0;
`;

export default function OldBryntumScheduler({
  config,
  events,
  shiftInstances,
  zoomLevel,
  startDate,
  endDate
}) {
  const schedulerRef = useRef();
  
useEffect(() => { const schedulerInstance = schedulerRef.current?.instance; schedulerInstance.events = events; }, [events]); useEffect(() => { const schedulerInstance = schedulerRef.current?.instance; schedulerInstance.timeRanges = shiftInstances; }, [shiftInstances]); useEffect(() => { const schedulerInstance = schedulerRef.current?.instance; resetScheduleTimeRange({ schedulerInstance, centerDate: new Date() }); }, [zoomLevel, startDate, endDate]); const resetScheduleTimeRange = ({ schedulerInstance, centerDate }) => { schedulerInstance.zoomTo({ ...zoomLevelMapping(zoomLevel), startDate, centerDate: centerDate || schedulerInstance.viewportCenterDate, endDate, }); }; return ( <SchedulerContainer className={classNames("spec-schedule", "b-react-scheduler-container")} id="bryntum-scheduler"> <BryntumScheduler ref={schedulerRef} {...config} /> </SchedulerContainer> ); }

I also tried to not just set events but call addEvent and removeEvent where needed but it made no difference.

Ill work on trying to break the demo in the same way


Post by alex.l »

Hi jasony,

import classNames from "classnames";
import styled from "styled-components";
import "components/common/Scheduler/Scheduler.styles.scss";
import { zoomLevelMapping } from "config/zoomConfig";

Please zip the whole app and attach here, included package.json (and CSS classes you mentioned in the code) to manage dependencies you used in the code, so we will be able to run the code with the data you used.

All the best,
Alex


Post by alex.l »

Ok, I read the thread and see you can't attach an app. Then please attach data you used to reproduce the issue, we will try to take all together and test it.

All the best,
Alex


Post by jasony »

Here is a sample of the data

[
{
        "resourceId": "238227c1-70a8-4f1d-bf01-cd44ae94407b",
        "id": "947dde53-ffba-4f0b-98c3-48bb1bcadea8",
        "startDate": "2023-01-04T16:15:00.000Z",
        "endDate": "2023-01-04T18:35:00.000Z",
        "draggable": true,
        "resizable": true,
        "eventStyle": "null"
    },
    {
        "resourceId": "238227c1-70a8-4f1d-bf01-cd44ae94407b",
        "id": "8bbced3d-8d67-4c84-a5a2-aa89f8a45d44",
        "startDate": "2023-01-04T22:05:00.000Z",
        "endDate": "2023-01-05T00:40:00.000Z",
        "draggable": true,
        "resizable": true,
        "eventStyle": "null"
    },
    {
        "resourceId": "a37ebf28-a590-40fe-86d0-6ff4fb97541e",
        "id": "f63f3653-d0c4-402a-b6f3-9b3d60b3739f",
        "startDate": "2023-01-04T13:00:00.000Z",
        "endDate": "2023-01-04T19:35:00.000Z",
        "draggable": true,
        "resizable": true,
        "eventStyle": "null"
    },
    {
        "resourceId": "d3eed3ba-9521-4190-b849-5ac82f8f1cc4",
        "id": "ac514ec5-4f3c-47ff-8e66-f313c865fb66",
        "startDate": "2023-01-05T09:50:00.000Z",
        "endDate": "2023-01-05T17:50:00.000Z",
        "draggable": true,
        "resizable": true,
        "eventStyle": "null"
    },
    {
        "resourceId": "d3eed3ba-9521-4190-b849-5ac82f8f1cc4",
        "id": "145c4a3d-7186-42b3-b4b4-6d3018630f35",
        "startDate": "2023-01-05T17:50:00.000Z",
        "endDate": "2023-01-05T21:50:00.000Z",
        "draggable": true,
        "resizable": true,
        "eventStyle": "null"
    },
    {
        "resourceId": "d3eed3ba-9521-4190-b849-5ac82f8f1cc4",
        "id": "70997653-87da-4e9f-a1c0-2ebf75c19b11",
        "startDate": "2023-01-05T21:50:00.000Z",
        "endDate": "2023-01-06T05:50:00.000Z",
        "draggable": true,
        "resizable": true,
        "eventStyle": "null"
    },
    {
        "resourceId": "d3eed3ba-9521-4190-b849-5ac82f8f1cc4",
        "id": "cfb41371-1364-40e2-a998-788fb3a448c2",
        "startDate": "2023-01-04T02:00:00.000Z",
        "endDate": "2023-01-04T06:00:00.000Z",
        "draggable": true,
        "resizable": true,
        "eventStyle": "null"
    },
    {
        "resourceId": "d3eed3ba-9521-4190-b849-5ac82f8f1cc4",
        "id": "f2a08d73-38f2-4d6c-bf57-a01241cab19f",
        "startDate": "2023-01-04T06:00:00.000Z",
        "endDate": "2023-01-04T07:45:00.000Z",
        "draggable": true,
        "resizable": true,
        "eventStyle": "null"
    },
    {
        "resourceId": "d3eed3ba-9521-4190-b849-5ac82f8f1cc4",
        "id": "32f4d224-2502-4426-9ed9-3f65ee3c7ea3",
        "startDate": "2023-01-04T07:45:00.000Z",
        "endDate": "2023-01-04T11:45:00.000Z",
        "draggable": true,
        "resizable": true,
        "eventStyle": "null"
    }
]

When a block gets changed/moved/created/deleted we post to a graphql backend and the backend fires subscriptions which we respond to. When we get the subscription, we update the Apollo cache and that in turns triggers the events we send in to change and causes a rerender.


Post by alex.l »

Hi jasony,

I checked the code and data provided. I applied all meaningful changes from your code snippets to our React example but was not able to reproduce the issue.
We need your further assistance here.

Please find "react-state" demo in /examples/frameworks/react/javascript folder and review the code. This should be a good point to start since it uses same approach as you described here. Try to apply required changes to that demo to reproduce the problem.

Please check if any warnings appeared in console before the error you posted here. It might be a warning about change properties that are not allowed to be changed at runtime. If so, then approach in react-state will definitely help you.

All the best,
Alex


Post by jasony »

We were unable to break the demo so far but we tried to put the demo code inside our app and got it down to the following:

/**
 * The App component
 */
import React, { useState, useRef, useCallback } from "react";
import "components/common/Scheduler/Scheduler.styles.scss";
import { Toast } from "@bryntum/scheduler";

import { BryntumScheduler } from "@bryntum/scheduler-react";

const BryntumSchedulerWrapper = () => {
  const initialIndex = 0;
  const schedulerRef = useRef(null);
  const theEvents = [
    {
      id: 1,
      resourceId: "r1",
      startDate: new Date(2021, 8, 3, 10),
      endDate: new Date(2021, 8, 3, 12),
      name: "Click me (dataset 0)",
      iconCls: "b-fa b-fa-mouse-pointer",
    },
    {
      id: 2,
      resourceId: "r2",
      startDate: new Date(2021, 8, 3, 12),
      endDate: new Date(2021, 8, 3, 13, 30),
      name: "Drag me",
      iconCls: "b-fa b-fa-arrows-alt",
    },
    {
      id: 3,
      resourceId: "r3",
      startDate: new Date(2021, 8, 3, 14),
      duration: 2,
      durationUnit: "h",
      name: "Double click me",
      eventColor: "purple",
      iconCls: "b-fa b-fa-mouse-pointer",
    },
    {
      id: 4,
      resourceId: "r4",
      startDate: new Date(2021, 8, 3, 8),
      endDate: new Date(2021, 8, 3, 11),
      name: "Right click me",
      iconCls: "b-fa b-fa-mouse-pointer",
    },
    {
      id: 5,
      resourceId: "r5",
      startDate: new Date(2021, 8, 3, 15),
      endDate: new Date(2021, 8, 3, 17),
      name: "Resize me",
      iconCls: "b-fa b-fa-arrows-alt-h",
    },
    {
      id: 6,
      resourceId: "r6",
      startDate: new Date(2021, 8, 3, 16),
      endDate: new Date(2021, 8, 3, 19),
      name: "Important meeting",
      iconCls: "b-fa b-fa-exclamation-triangle",
      eventColor: "red",
    },
    {
      id: 7,
      resourceId: "r6",
      startDate: new Date(2021, 8, 3, 6),
      endDate: new Date(2021, 8, 3, 8),
      name: "Sports event",
      iconCls: "b-fa b-fa-basketball-ball",
    },
    {
      id: 8,
      resourceId: "r7",
      startDate: new Date(2021, 8, 3, 9),
      endDate: new Date(2021, 8, 3, 11, 30),
      name: "Dad's birthday!",
      iconCls: "b-fa b-fa-birthday-cake",
      // Custom styling from data
      style: "background-color : teal; font-size: 18px",
      // Prevent default styling
      eventStyle: "none",
    },
  ];

  const theResources = [
    { id: "r1", name: "Mike" },
    { id: "r2", name: "Linda" },
    { id: "r3", name: "Chang" },
    { id: "r4", name: "Kate" },
    { id: "r5", name: "Lisa" },
    { id: "r6", name: "Steve" },
    { id: "r7", name: "Mark" },
    { id: "r8", name: "Madison" },
    { id: "r9", name: "Hitomi" },
    { id: "r10", name: "Dan" },
  ];

  const [index, setIndex] = useState(initialIndex);
  const [resources, setResources] = useState(theResources);
  const [events, setEvents] = useState(theEvents);

  // Called when data changes in any of the Scheduler stores
  const onDataChange = useCallback(({ store, action, records }) => {
    if ("dataset" !== action && store.changes) {
      Toast.show(`
            <h3>${store.storeId} changed</h3>
            Added: <strong>${store.changes.added.length}</strong>
            Modified: <strong>${store.changes.modified.length}</strong>
            Removed: <strong>${store.changes.removed.length}</strong>
            `);
    }
  }, []);

  const schedulerConfig = {
    viewPreset: "hourAndDay",
    startDate: new Date(2021, 8, 3, 6),
    endDate: new Date(2021, 8, 3, 20),
    resourceImagePath: "users/",
    eventStore: {
      syncDataOnLoad: true,
    },
    columns: [{ type: "resourceInfo", text: "Staff", field: "name" }],
  };

  return (
    <>
      <BryntumScheduler
        {...schedulerConfig}
        events={events}
        resources={resources}
        ref={schedulerRef}
        onDataChange={onDataChange}
      />
    </>
  );
};

export default BryntumSchedulerWrapper;

We do not send in any props or anything else from our app. However if the parent component re-renders causing a rerender in this component all the events disappear. I attach a video of this to this message.

We attempted to reproduce it in the demo app but for some reason we were unable to reproduce it when re-rendering in that app.

In the attached video you will see there are some warnings that appear at the start but the app still works. As soon as we change the date (we ripped out all the code so it just rerenders the component but doesn't change the actual dates), the event disappear and everytime we try to use the scheduler we get those errors. You can ignore the popper error that showed up, that is related to the date dropdown

Attachments
Screen Recording 2023-01-06 at 13.49.30.mov
(12.43 MiB) Downloaded 23 times

Post by alex.l »

I reviewed your code and video provided. It's great you was able to minimize the code used to reproduce that. Please attach a runnable test case, so we will be able to debug this problem. Unfortunately, I can't suggest you something meaningful without debugging.
First step - please try to comment the code causes the error in console. As you know, it might be critical for JavaScript thread.

All the best,
Alex


Post Reply