scheduler reloading after adding removing or updating events

I am using ReactJs functional components along with redux. All the events are stored in a redux state. When the events are created, updated or deleted I want those to be reflected in the redux state too so I am dispatching these actions in the onActionBegin function if they are created, updated or deleted. But the problem happening here is it is not updating the redux state and is giving this in the console.

So I tried using useState to set up a new array that has all the events that are in the redux state like this


This finally worked but after I create, update or delete an event, now the scheduler reloads and I don't want it to do that. So how can I achieve that?


Also, I am using dialog component along with the scheduler. Whenever I click on an eventCell which has isBooked value set to true, I want that modal to open. Again, it is working, but the entire scheduler reloads when it opens or closes. So please look into that too.


Here is my code file:

import React, { useEffect, useRef, useState } from 'react';
import { ScheduleComponent, Day, Week, DragAndDrop, Inject, ViewsDirective, ViewDirective,
RecurrenceEditorComponent } from '@syncfusion/ej2-react-schedule';
import { DropDownListComponent } from '@syncfusion/ej2-react-dropdowns';
import { L10n } from '@syncfusion/ej2-base';
import { DateTimePickerComponent } from '@syncfusion/ej2-react-calendars';
import moment from 'moment';
import { DialogComponent } from '@syncfusion/ej2-react-popups';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import { useDispatch, useSelector } from 'react-redux';
import { createSchedule, deleteMhpSchedule, updateSchedule, requestReschedule, cancelRequestReschedule }
from '../../actions/schedule';
import './ScheduleCalendar.scss';

L10n.load({
  'en-US': {
    schedule: {
      saveButton: 'Save',
      cancelButton: 'Cancel',
      deleteButton: 'Remove',
      newEvent: 'Add Time Slot',
      editEvent: 'Update Time Slot',
      deleteEvent: 'Delete Time Slot',
    },
  },
});

function ScheduleCalendar() {
  const scheduleObj = useRef();
  const recurrenceObj = useRef();
  const dispatch = useDispatch();
  const user = JSON.parse(localStorage.getItem('profile'))?.user;
  const [visibility, setDialogVisibility] = useState('');
  const { schedules } = useSelector((state) => state.schedules);
  const [timeSlots, setTimeSlots] = useState([]);

  useEffect(() => {
    setTimeSlots(schedules);
  }, []);

  const dialogClose = () => {
    setDialogVisibility(false);
  };

  const onActionBegin = (args) => {
    if (args.requestType === 'eventCreate') {
      dispatch(createSchedule({
        startTime: args.data[0].startTime.toISOString(),
        endTime: args.data[0].endTime.toISOString(),
        sessionType: args.data[0].sessionType,
        mhp: user.id,
      }));
    } else if (args.requestType === 'eventChange') {
      if (args.data.isBooked) {
        // eslint-disable-next-line
        args.cancel = true;
      } else {
        dispatch(updateSchedule(args.data.id, {
          startTime: args.data.startTime.toISOString(),
          endTime: args.data.endTime.toISOString(),
          sessionType: args.data.sessionType,
        }));
      }
    } else if (args.requestType === 'eventRemove') {
      if (args.data.isBooked) {
        // eslint-disable-next-line
        args.cancel = true;
      } else {
        dispatch(deleteMhpSchedule(args.data[0].id));
      }
    }
  };

  const sendRescheduleRequest = (scheduleId) => {
    dispatch(requestReschedule(scheduleId))
      .then(() => {
        setDialogVisibility(false);
      });
  };

  const cancelRescheduleRequest = (scheduleId) => {
    dispatch(cancelRequestReschedule(scheduleId));
  };

  const rescheduleFooter = (props) => {
    return (
      <button
        type="button"
        className="e-control e-btn e-lib rescheduleModal__button e-primary e-flat"
        onClick={() => {
          if (!props.rescheduleRequested) {
            sendRescheduleRequest(props.id);
          } else {
            cancelRescheduleRequest(props.id);
          }
        }}
      >
        {props.rescheduleRequested ? 'Cancel Reschedule Request' : 'Request to Reschedule'}
      </button>
    );
  };

  const eventTemplate = (props) => {
    if (props.sessionType === 'Therapy Session') {
      if (props.isBooked) {
        return (
          <div onClick={() => setDialogVisibility(props.id)} role="button">
            <div className="schedulePage__schedulerEvent" style={{ background: '#AB70CA' }} role="button">
              <div>
                {props.rescheduleRequested ? (
                  <div>
                    Reschedule
                    <br />
                    Requested
                  </div>
                ) : 'Booked'}
              </div>
            </div>
            <DialogComponent
              width="400px"
              target="#dialog-target-schedule"
              isModal
              animationSettings={{ effect: 'Zoom', duration: 400, delay: 0 }}
              close={() => dialogClose()}
              header=""
              visible={visibility === props.id}
              showCloseIcon
              className="reschedule__modal"
              footerTemplate={() => rescheduleFooter(props)}
            >
              <div className="modal__clientProfile">
                <img src={props.user.image ? props.user.image.url : ''} alt="client profile pic" />
                <h2>{props.user.displayName}</h2>
              </div>
              <div className="modal__clientSchedule">
                <AccessTimeIcon />
                <h4>{moment(props.startTime.toString()).utc().format('MMMM DD, YYYY')}</h4>
                <div className="modal__clientTime">
                  <h5>{moment(props.startTime.toString()).format('h:mma')}</h5>
                  <div className="modal__timeBar" />
                  <h5>{moment(props.endTime.toString()).format('h:mma')}</h5>
                </div>
              </div>
            </DialogComponent>
          </div>
        );
      }
      return (
        <div className="schedulePage__schedulerEvent" style={{ background: '#AB70CA' }} />
      );
    }

    if (props.isBooked) {
      return (
        <div onClick={() => setDialogVisibility(props.id)} role="button">
          <div className="schedulePage__schedulerEvent" style={{ background: '#FA8A8A' }} role="button">
            <div>Booked</div>
          </div>
          <DialogComponent
            width="400px"
            target="#dialog-target-schedule"
            isModal
            animationSettings={{ effect: 'Zoom', duration: 400, delay: 0 }}
            close={() => dialogClose()}
            header=""
            visible={visibility === props.id}
            showCloseIcon
            className="reschedule__modal"
            footerTemplate={() => rescheduleFooter(props)}
          >
            <div className="modal__clientProfile">
              <img src={props.user.image ? props.user.image.url : ''} alt="client profile pic" />
              <h2>{props.user.displayName}</h2>
            </div>
            <div className="modal__clientSchedule">
              <AccessTimeIcon />
              <h4>{moment(props.startTime.toString()).utc().format('MMMM DD, YYYY')}</h4>
              <div className="modal__clientTime">
                <h5>{moment(props.startTime.toString()).format('h:mma')}</h5>
                <div className="modal__timeBar" />
                <h5>{moment(props.endTime.toString()).format('h:mma')}</h5>
              </div>
            </div>
          </DialogComponent>
        </div>
      );
    }
    return (
      <div className="schedulePage__schedulerEvent" style={{ background: '#FA8A8A' }} />
    );
  };

  const onSessionTypeChange = (args) => {
    if (args.itemData !== null) {
      const startObj = document.querySelector('#startTime').ej2_instances[0];
      const endObj = document.querySelector('#endTime').ej2_instances[0];
      if (args.itemData.value === 'Consultation Call') {
        endObj.value = new Date(startObj.value.getTime() + 15 * 60000);
      } else {
        endObj.value = new Date(startObj.value.getTime() + user.sessionDuration * 60000);
      }
    }
  };

  const onStartTimeChange = (args) => {
    const sessionObj = document.querySelector('#sessionType').ej2_instances[0];
    const endObj = document.querySelector('#endTime').ej2_instances[0];
    if (sessionObj.value === 'Consultation Call') {
      endObj.value = new Date(args.value.getTime() + 15 * 60000);
    } else {
      endObj.value = new Date(args.value.getTime() + user.sessionDuration * 60000);
    }
  };

  const editorTemplate = (props) => {
    return (props !== undefined ? (
      <table className="custom-event-editor" style={{ width: '100%', cellpadding: '5' }}>
        <tbody>
          <tr>
            <td className="e-textlabel">Session Type</td>
            <td className="custom-dropdown" colSpan={4}>
              <DropDownListComponent
                id="sessionType"
                placeholder="Session Type"
                data-name="sessionType"
                className="e-field"
                style={{ width: '100%' }}
                dataSource={[
                  { text: 'Therapy Session', value: 'Therapy Session' },
                  { text: 'Consultation Call', value: 'Consultation Call' },
                ]}
                fields={{ text: 'text', value: 'value' }}
                value={props.sessionType || 'null'}
                change={onSessionTypeChange}
              />
            </td>
          </tr>
          <tr>
            <td className="e-textlabel">Start Time</td>
            <td colSpan={4}>
              <DateTimePickerComponent
                format="dd/MM/yy hh:mm a"
                step={user.sessionDuration}
                id="startTime"
                data-name="startTime"
                value={new Date(props.startTime || props.StartTime)}
                className="e-field"
                change={onStartTimeChange}
              />
            </td>
          </tr>
          <tr>
            <td className="e-textlabel">End Time</td>
            <td colSpan={4}>
              <DateTimePickerComponent
                format="dd/MM/yy hh:mm a"
                step={user.sessionDuration}
                id="endTime"
                data-name="endTime"
                value={new Date(props.endTime || props.EndTime)}
                className="e-field"
              />
            </td>
          </tr>
          <tr>
            <td className="e-textlabel">Recurrence</td>
            <td colSpan={4}>
              <RecurrenceEditorComponent ref={recurrenceObj} id="RecurrenceEditor" />
            </td>

          </tr>
        </tbody>
      </table>
    ) : <div />);
  };

  const onPopupOpen = (args) => {
    if (args.data.isBooked) {
      // eslint-disable-next-line
      args.cancel = true;
    } else if (args.type === 'Editor') {
      scheduleObj.current.eventWindow.recurrenceEditor = recurrenceObj.current;
      const statusElement = args.element.querySelector('#sessionType');
      statusElement.setAttribute('name', 'sessionType');
      const sessionObj = document.querySelector('#sessionType').ej2_instances[0];
      sessionObj.value = args.data.sessionType ? args.data.sessionType : 'Therapy Session';
      const endObj = document.querySelector('#endTime').ej2_instances[0];
      endObj.value = args.data.endTime ? args.data.endTime : new Date(endObj.value.getTime() +
(user.sessionDuration - 30) * 60000);
    }
  };

  const onCellClick = (args) => {
    scheduleObj.current.openEditor(args, 'Add');
  };

  const onEventClick = (args) => {
    if (!args.event.RecurrenceRule) {
      scheduleObj.current.openEditor(args.event, 'Save');
    } else {
      scheduleObj.current.quickPopup.openRecurrenceAlert();
    }
  };

  return (
    <ScheduleComponent
      ref={scheduleObj}
      showQuickInfo={false}
      eventClick={onEventClick}
      cellClick={onCellClick}
      eventSettings={{
        dataSource: timeSlots,
        template: eventTemplate,
        fields: {
          id: 'id',
          isBooked: 'isBooked',
          sessionType: { name: 'sessionType', validation: { required: true } },
          startTime: { name: 'startTime', validation: { required: true } },
          endTime: { name: 'endTime', validation: { required: true } },
        },
      }}
      popupOpen={onPopupOpen}
      actionBegin={onActionBegin}
      editorTemplate={editorTemplate}
      className="schedulePage__scheduler"
      width="100%"
      height="75vh"
      selectedDate={new Date()}
      enablePersistence
    >
      <ViewsDirective>
        <ViewDirective option="Week" />
        <ViewDirective option="Day" />
      </ViewsDirective>
      <Inject services={[Day, Week, DragAndDrop]} />
    </ScheduleComponent>
  );
}

export default ScheduleCalendar;


7 Replies

BS Balasubramanian Sattanathan Syncfusion Team March 3, 2022 12:04 PM UTC

Hi Mansi,

We have tried to validate the error by preparing the sample with your given code snippet. But we couldn't get the sample completed since it's throwing the "react-redux" related error. So, can you please complete the sample in the below link which will be very helpful to validate the issue and provide a prompt solution at the earliest?

Sample: https://stackblitz.com/edit/add-remove-resource-iycmhv?file=components%2Fchild.js

Regards,
Balasubramanian S



MS Mansi Sharma March 3, 2022 05:41 PM UTC



BS Balasubramanian Sattanathan Syncfusion Team March 9, 2022 05:44 PM UTC

Hi Mansi,

We have tried to replicate the scheduler reload related issue at our end in your given sample. But unfortunately, we couldn't reproduce the problem since it's not refreshing the scheduler. So, can you please confirm the issue you are mentioning after adding the appointment the scheduler got refreshed?

Please find the video demo that works as expected in the attachment.

Regards,
Balasubramanian S


Attachment: video_demo_55181b21.zip


MS Mansi Sharma March 10, 2022 12:15 AM UTC

At the end of this video demo when you created an event the scheduler blinked after some time or reloaded as you can see. That's what I'm talking about.



BS Balasubramanian Sattanathan Syncfusion Team March 11, 2022 07:55 PM UTC

Hi Mansi,


We let you know that you have assigned the appointment data through the react hooks and performed the CRUD actions on your own by disabling the Scheduler default CRUD actions. And also you have updated the data source using the useEffect hook for every CRUD action. So, the component will get re-render and causes the reloading of the page. We suggest adding a custom spinner for these asynchronous actions until the scheduler gets updated to have UI friendly.


Regards,

Balasubramanian S



MC Marketing CodeSpirit March 11, 2024 08:52 PM UTC

I am facing the same issue, did you managed to get it work?




AK Ashokkumar Karuppasamy Syncfusion Team March 12, 2024 02:22 PM UTC

Hi Mansi Sharma,

We suspect that you may be utilizing the schedule template on your end, which can trigger a re-render whenever there is a state value change. To address this, we suggest incorporating useCallback in your schedule templates. Additionally, you can manage schedule property values by using useState and update the schedule accordingly. If you are still facing issues, kindly share more details on state variables and the scenarios in which you have changed the state or replicate the issue in our shared sample. Please give it a try, and feel free to reach out if you need any further assistance.

Sample: Attached as a zip file

For your reference, avoid triggering unnecessary re-renders of the schedule:

 
let scheduleRef = useRef(null);

  const [data, setData] = useState(localData);

  let onCreated = () => {

    scheduleRef.current.showHeaderBar = false;

  };

      selectedDate={new Date(2017, 5, 5)}

      eventSettings={data}


 const cellTemplate = useCallback((props) => {

    console.log(props.groupIndex);

    // Your other logic here

    return "";

  }, []);

 


Regards,
Ashok


Attachment: schedulercomponent_fa5ca870_5ff49509.zip

Loader.
Up arrow icon