Updating Events/Invalids every minute

Hello everyone,
I need to update events and invalid ranges every minute. So far, I have come up with a solution that involves a timer running every minute to perform the desired updates. However, I’m having trouble with updating the invalid ranges. It always scrolls back to the top, and I can’t find a way to retain the user’s scroll position.

Here’s some context: We use the event calendar for a time tracking system, and employees can add events for up to five hours into the future. The core problem is that if a user keeps the page open, the set constraint doesn’t update. Eventually, the user is forced to reload the page.

Has anyone implemented a similar feature who might be able to help me?

Hi @Neklas,

Could you share a code example showing how you’re currently updating the events and the invalid ranges?

Hi @gabi,

please excuse my late response.
The following snippet is a simplified version of the code, as I’m unfortunately not able to share the full implementation.
It seems to work now, both in this minimal example and in the more complex version (at least I cannot reproduce the issue anymore).
If you happen to know a better way to achieve this, I’d really appreciate your suggestions.

import { useCallback, useEffect, useRef, useState } from 'react';

import { Eventcalendar } from '@mobiscroll/react';
import moment from 'moment';

const MINUTE_IN_MILLISECONDS = 60_000;

/**
 * @param {() => void} callback
 */
export function useOneMinuteTimer(callback) {
  const timeout = useRef(null);
  const interval = useRef(null);

  const isTimerRunning = useCallback(() => {
    return timeout.current !== null && interval.current !== null;
  }, []);

  const stopTimer = useCallback(() => {
    if (timeout.current !== null) {
      clearTimeout(timeout.current);
      timeout.current = null;
    }

    if (interval.current !== null) {
      clearInterval(interval.current);
      interval.current = null;
    }
  }, []);

  const startTimer = useCallback(() => {
    if (!isTimerRunning()) {
      timeout.current = setTimeout(() => {
        callback();

        interval.current = setInterval(() => {
          callback();
        }, MINUTE_IN_MILLISECONDS);
      }, MINUTE_IN_MILLISECONDS - new Date().getSeconds() * 1000 - new Date().getMilliseconds());
    }
  }, [isTimerRunning, callback]);

  useEffect(() => {
    startTimer();

    return () => {
      stopTimer();
    };
  }, [startTimer, stopTimer]);
}

export function CalendarExample() {
  const [invalid, setInvalid] = useState({
    start: moment().add(5, 'hours').toDate(),
    end: moment().add(5, 'hours').endOf('day').toDate(),
  });
  const [event, setEvent] = useState({
    start: moment().subtract(30, 'minutes').toDate(),
    end: moment().toDate(),
    text: 'Event',
  });
  const [selectedDate, setSelectedDate] = useState(new Date());

  useOneMinuteTimer(() => {
    const now = new Date();
    setEvent((ev) => ({
      ...ev,
      end: now,
    }));
    setInvalid((inv) => ({
      start: moment(now).add(5, 'hours').toDate(),
      end: moment(now).add(5, 'hours').endOf('day').toDate(),
    }));
  });

  return (
    <Eventcalendar
      selectedDate={selectedDate}
      onSelectedDateChange={({ date }) => setSelectedDate(date)}
      view={{
        schedule: {
          type: 'week',
          timeCellStep: 10,
          timeLabelStep: 5,
        },
      }}
      timeFormat="HH:mm"
      data={[event]}
      invalid={[
        {
          recurring: {
            repeat: 'daily',
            until: moment().subtract(6, 'days').toDate(),
            interval: 1,
          },
        },
        invalid,
      ]}
      dragTimeStep={5}
    />
  );
}