---
title: "React Scheduler Prevent Past Event Creation Example | Mobiscroll"
component: "Scheduler"
framework: "React"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/react/scheduler/disallow-past-event-creation"
---

# Prevent Past Event Creation - React Scheduler demo

Block event creation and editing on past dates in the React Scheduler — past dates are blocked, past events are locked, and failed attempts show a toast. For React and Next.JS.

- [Live demo](https://demo.mobiscroll.com/react/scheduler/disallow-past-event-creation)
- [View React JS code on GitHub](https://github.com/acidb/mobiscroll-demos-react/tree/main/src/demos/eventcalendar/scheduler/disallow-past-event-creation)
- [View React TS code on GitHub](https://github.com/acidb/mobiscroll-demos-react-ts/tree/main/src/demos/eventcalendar/scheduler/disallow-past-event-creation)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/scheduler/disallow-past-event-creation.md), [jQuery](https://demo.mobiscroll.com/jquery/scheduler/disallow-past-event-creation.md), [Angular](https://demo.mobiscroll.com/angular/scheduler/disallow-past-event-creation.md), [Vue](https://demo.mobiscroll.com/vue/scheduler/disallow-past-event-creation.md)

## Description

Sometimes we don't want users to be able to create events in the past or to alter past events. This might be role based in some cases or applied in general to the calendar.

To disable past event creation and manipulation, a couple of things need to be handled:

- **Invalidate past dates & times** - this takes care of validating event drop and event creation on past dates and times that are before the past hour
- **Mark past events as fixed** - to do this, set the `editable` property of the event object to `false` for the events that should remain fixed
- **Handle past occurrences of recurring events** - recurring events are loaded as a single event and the occurrences are generated by the scheduler.

Moving the past occurrences need to be handled in the [`onEventUpdate`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventUpdate) and [`onEventCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreate) lifecycle events

- **Give feedback to the user** - optionally, a toast can be displayed to give feedback to the user why an event cannot be dropped, moved and created in the past. For this we can use the [`onEventCreateFailed`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreateFailed) and [`onEventUpdateFailed`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventUpdateFailed) lifecycle events

## What this demo shows

- A desktop weekly scheduler layout with a fixed week strip at the top, a fixed all-day row below it, and a scrollable scheduler time grid for the selected week.
- **Header navigation** The month and year label in the top left opens date navigation, while the blue previous and next arrows and the Today button on the right make it easy to move between weeks and jump back to the current day.
- **Week view** The fixed strip below the header displays the days of the selected week, with the current date highlighted by a blue circle.
- **All-day events** All-day events appear in a dedicated row that stays fixed above the time grid.
- **Time grid** The scheduler grid scrolls vertically through the hours of the selected week.
- **Past dates** Past dates are disabled and rendered with a gray background.
- **Disabled past days** Disabled past dates block event creation and drag and drop interactions.
- **Past events** Events on past dates remain visible, but they cannot be moved or repositioned.
- **Failed past updates** If a user tries to move a past recurring event, a toast appears at the bottom center with the message `Can't move past events.`
- **Timed events** Timed events are displayed in the weekly scheduler grid as colored cards with a colored stripe on the left, the event title in bold, and the exact start and end time displayed above the title.
- **Event overlapping** If events overlap, the scheduler places them side by side without hiding any of them or causing other conflicts.
- **Current time** A blue current-time line appears across the time grid, with a small blue dot marking the current date.
- **Hover behavior** Hovering over the time grid shows a time indicator that follows the cursor in 15-minute increments.
- **Event interaction** Hovering over events highlights them and shows drag and resize handles for repositioning or changing duration.
- **Event selection** Clicking an event selects and highlights it.
- **Event creation** Users can create events on future dates/hours by double-clicking the time grid or clicking and dragging on it.

## Best for

- **Booking systems** Preventing users from creating appointments in dates or times that have already passed.
- **Resource scheduling** Keeping completed or historical assignments visible while blocking accidental edits.
- **Workforce planning** Protecting past shifts, jobs, or allocations after the scheduled date has passed.
- **Service operations** Allowing teams to review past work without changing records that should remain fixed.
- **Audit-sensitive calendars** Separating future scheduling changes from historical event data that should not be moved or resized.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-themeVariant) - option
- [`view`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-view) - option
- [`data`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-data) - option
- [`invalid`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-invalid) - option
- [`clickToCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-clickToCreate) - option
- [`dragToCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-dragToCreate) - option
- [`dragToMove`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-dragToMove) - option
- [`dragToResize`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-dragToResize) - option
- [`onEventCreateFailed`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreateFailed) - event
- [`onEventUpdateFailed`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventUpdateFailed) - event
- [`onEventCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreate) - event
- [`onEventUpdate`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventUpdate) - event
- [`onClose`](https://mobiscroll.com/docs/react/notifications/toast#event-onClose) - event

## Code

### JSX

```jsx
import { Eventcalendar, getJson, setOptions, Toast } from '@mobiscroll/react';
import { useCallback, useEffect, useMemo, useState } from 'react';

setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

const now = new Date();
const today = new Date(now.setMinutes(59));
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);

function App() {
  const [myEvents, setEvents] = useState([]);
  const [isToastOpen, setToastOpen] = useState(false);
  const [toastMessage, setToastMessage] = useState('');

  const myInvalid = useMemo(
    () => [
      {
        recurring: {
          repeat: 'daily',
          until: yesterday,
        },
      },
      {
        start: yesterday,
        end: today,
      },
    ],
    [],
  );

  const myView = useMemo(
    () => ({
      scheduler: {
        type: 'week',
      },
    }),
    [],
  );

  const handleEventCreateFailed = useCallback((args) => {
    if (!args.originEvent) {
      setToastMessage("Can't create event in the past");
      setToastOpen(true);
    }
  }, []);

  const handleEventUpdateFailed = useCallback((args) => {
    if (!args.oldEventOccurrence) {
      setToastMessage("Can't move event in the past");
      setToastOpen(true);
    }
  }, []);

  const handleEventCreate = useCallback((args) => {
    const oldEvent = args.originEvent;
    const start = oldEvent && oldEvent.start ? oldEvent.start : null;

    // Handle recurring events
    if (start && start < today) {
      setToastMessage("Can't move past event");
      setToastOpen(true);
      return false;
    }
  }, []);

  const handleEventUpdate = useCallback((args) => {
    const oldEvent = args.oldEvent;
    const start = oldEvent && oldEvent.start ? oldEvent.start : null;
    const oldEventOccurrence = args.oldEventOccurrence;
    const occurrenceStart = oldEventOccurrence && oldEventOccurrence.start ? oldEventOccurrence.start : null;

    // Handle recurring events
    if ((start && start < today) || (occurrenceStart && occurrenceStart < today)) {
      return false;
    }
  }, []);

  const handleCloseToast = useCallback(() => {
    setToastOpen(false);
  }, []);

  useEffect(() => {
    getJson(
      'https://trial.mobiscroll.com/events/?vers=5',
      (events) => {
        for (const event of events) {
          // Convert dates to date objects
          event.start = event.start ? new Date(event.start) : event.start;
          event.end = event.end ? new Date(event.end) : event.end;
          // Mark past events as fixed by setting the event.editable property to false
          event.editable = event.start && today < event.start;
        }
        setEvents(events);
      },
      'jsonp',
    );
  }, []);

  return (
    <>
      <Eventcalendar
        className="md-disallow-past-event-creation"
        view={myView}
        data={myEvents}
        invalid={myInvalid}
        clickToCreate={true}
        dragToCreate={true}
        dragToMove={true}
        dragToResize={true}
        onEventCreateFailed={handleEventCreateFailed}
        onEventUpdateFailed={handleEventUpdateFailed}
        onEventCreate={handleEventCreate}
        onEventUpdate={handleEventUpdate}
      />
      <Toast isOpen={isToastOpen} message={toastMessage} onClose={handleCloseToast} />
    </>
  );
}

export default App;
```

### TSX

```tsx
import {
  Eventcalendar,
  getJson,
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscEventCreateEvent,
  MbscEventCreateFailedEvent,
  MbscEventUpdateEvent,
  MbscEventUpdateFailedEvent,
  setOptions,
  Toast,
} from '@mobiscroll/react';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';

setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

const now = new Date();
const today = new Date(now.setMinutes(59));
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);

const App: FC = () => {
  const [myEvents, setEvents] = useState<MbscCalendarEvent[]>([]);
  const [isToastOpen, setToastOpen] = useState(false);
  const [toastMessage, setToastMessage] = useState('');

  const myInvalid = useMemo(
    () => [
      {
        recurring: {
          repeat: 'daily',
          until: yesterday,
        },
      },
      {
        start: yesterday,
        end: today,
      },
    ],
    [],
  );

  const myView = useMemo<MbscEventcalendarView>(
    () => ({
      scheduler: {
        type: 'week',
      },
    }),
    [],
  );

  const handleEventCreateFailed = useCallback((args: MbscEventCreateFailedEvent) => {
    if (!args.originEvent) {
      setToastMessage("Can't create event in the past");
      setToastOpen(true);
    }
  }, []);

  const handleEventUpdateFailed = useCallback((args: MbscEventUpdateFailedEvent) => {
    if (!args.oldEventOccurrence) {
      setToastMessage("Can't move event in the past");
      setToastOpen(true);
    }
  }, []);

  const handleEventCreate = useCallback((args: MbscEventCreateEvent) => {
    const oldEvent = args.originEvent;
    const start = oldEvent && oldEvent.start ? oldEvent.start : null;

    // Handle recurring events
    if (start && start < now) {
      setToastMessage("Can't move past event");
      setToastOpen(true);
      return false;
    }
  }, []);

  const handleEventUpdate = useCallback((args: MbscEventUpdateEvent) => {
    const oldEvent = args.oldEvent;
    const start = oldEvent && oldEvent.start ? oldEvent.start : null;
    const oldEventOccurrence = args.oldEventOccurrence;
    const occurrenceStart = oldEventOccurrence && oldEventOccurrence.start ? oldEventOccurrence.start : null;

    // Handle recurring events
    if ((start && start < now) || (occurrenceStart && occurrenceStart < now)) {
      return false;
    }
  }, []);

  const handleCloseToast = useCallback(() => {
    setToastOpen(false);
  }, []);

  useEffect(() => {
    getJson(
      'https://trial.mobiscroll.com/events/?vers=5',
      (events) => {
        for (const event of events) {
          // Convert dates to date objects
          event.start = event.start ? new Date(event.start) : event.start;
          event.end = event.end ? new Date(event.end) : event.end;
          // Mark past events as fixed by setting the event.editable property to false
          event.editable = event.start && today < event.start;
        }
        setEvents(events);
      },
      'jsonp',
    );
  }, []);

  return (
    <>
      <Eventcalendar
        className="md-disallow-past-event-creation"
        view={myView}
        data={myEvents}
        invalid={myInvalid}
        clickToCreate={true}
        dragToCreate={true}
        dragToMove={true}
        dragToResize={true}
        onEventCreateFailed={handleEventCreateFailed}
        onEventUpdateFailed={handleEventUpdateFailed}
        onEventCreate={handleEventCreate}
        onEventUpdate={handleEventUpdate}
      />
      <Toast isOpen={isToastOpen} message={toastMessage} onClose={handleCloseToast} />
    </>
  );
};

export default App;
```

### CSS

```css
.md-disallow-past-event-creation .mbsc-readonly-event {
  opacity: 0.6;
}
```

## Implementation notes

- Use `view: { scheduler: { type: 'week' } }`. Enable all four interaction modes: `clickToCreate: true`, `dragToCreate: true`, `dragToMove: true`, `dragToResize: true`.
- Compute two date references at module level: `today = new Date(now.setMinutes(59))` (current time with minutes snapped to `:59` - represents the boundary of the blocked past range) and `yesterday` (midnight of the previous day).
- Pass an `invalid` array with two entries: `{ recurring: { repeat: 'daily', until: yesterday } }` (blocks all day slots through yesterday) and `{ start: yesterday, end: today }` (a continuous block from midnight yesterday through the current `:59` mark, covering all of yesterday and the elapsed portion of today). Together these prevent creating or dropping events at any past time.
- Load events via JSONP. After loading, convert `event.start` and `event.end` to `Date` objects (`new Date(event.start)`), then set `event.editable = event.start && today < event.start` - only future events are editable; past events get `editable: false`. For the imperative API, call `inst.setEvents(events)`.
- **`onEventCreateFailed`** - show a toast ("Can't create event in the past") only when `args.originEvent` is absent (guards against false positives from recurring events).
- **`onEventUpdateFailed`** - show a toast ("Can't move event in the past") only when `args.oldEventOccurrence` is absent.
- **`onEventCreate`** - handle the recurring event edge case: if `args.originEvent?.start` is before `today`, show a toast ("Can't move past event") and `return false`. In React/Angular/Vue, `return false` alone is sufficient to restore the event - the bound array was never mutated. For the imperative API (JS/jQuery), the events array isn't bound to state, so also call `inst.updateEvent(args.originEvent)` to restore the event before returning false.
- **`onEventUpdate`** - if `args.oldEvent.start < today` or `args.oldEventOccurrence?.start < today`, `return false`. In React/Angular/Vue, `return false` alone is sufficient to restore the event - the bound array was never mutated. For the imperative API (JS/jQuery), the events array isn't bound to state, so also call `inst.updateEvent(args.oldEvent)` to restore the event.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-react.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config react
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   npm run dev
   ```

On a trial, add `--trial` to the config command: `mobiscroll config react --trial`.

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/react/getting-started/installation

## Related demos

- Previous: [Recurring Event Add & Edit](https://demo.mobiscroll.com/react/scheduler/recurring-event-add-edit-dialog.md) - Add a recurrence editor to the React Scheduler event dialog — with daily, weekly, monthly, and custom presets plus an RRULE builder.
