---
title: "React Event calendar Add, Edit and Delete Events Example | Mobiscroll"
component: "Event calendar"
framework: "React"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/react/eventcalendar/create-read-update-delete-CRUD"
---

# Add, Edit and Delete Events - React Event calendar demo

Add full event management to a React event calendar — create events by drag or click, edit them in a custom dialog, and delete with undo. For React and Next.JS.

- [Live demo](https://demo.mobiscroll.com/react/eventcalendar/create-read-update-delete-CRUD)
- [View React JS code on GitHub](https://github.com/acidb/mobiscroll-demos-react/tree/main/src/demos/eventcalendar/calendar-view/create-read-update-delete-CRUD)
- [View React TS code on GitHub](https://github.com/acidb/mobiscroll-demos-react-ts/tree/main/src/demos/eventcalendar/calendar-view/create-read-update-delete-CRUD)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/eventcalendar/create-read-update-delete-CRUD.md), [jQuery](https://demo.mobiscroll.com/jquery/eventcalendar/create-read-update-delete-CRUD.md), [Angular](https://demo.mobiscroll.com/angular/eventcalendar/create-read-update-delete-CRUD.md), [Vue](https://demo.mobiscroll.com/vue/eventcalendar/create-read-update-delete-CRUD.md)

## Description

**Create new events**

Drag to create and click to create is enabled. Events can be created by dragging or with double clicks. As soon as the initial position is confirmed, a temporary event is created and a custom edit dialog is shown for refinement. On cancel the temporary event will be removed and on confirmation the event will stay in the calendar.

**Edit existing events**

Drag to resize and drag to move is enabled. Events can be reordered and resized. In addition to that, clicking on the event will open a custom dialog that enables editing the various properties.

**Delete events**

Delete can be implemented inside the edit dialog with a button. It's just a matter of removing it from the data object. If a dialog is not shown on click, focused events can be deleted with the *backspace* and *delete* keys.

**Interested in adding recurrence configuration?** [Check out the next example →](https://demo.mobiscroll.com/react/eventcalendar/recurring-event-add-edit-dialog)

## What this demo shows

- A full month event calendar with multiple events and the standard header controls for month and year navigation, previous and next arrows, and a Today button.
- **Event creation** Users can create events by clicking and dragging across calendar cells or by double-clicking a day cell.
- **New event dialog** Creating an event opens a dialog with fields for title, description, all-day, start and end date selection, travel time, event color, and busy or free status.
- **Date selection** The start and end inputs open a date picker calendar with range selection.
- **Cancel behavior** Clicking outside the dialog or pressing Cancel closes it and removes the temporary event from the calendar.
- **Event editing** Clicking or tapping an existing event opens the same dialog in edit mode with the current values prefilled.
- **Editable properties** In edit mode, the event details and event color can be changed before saving.
- **Save and close behavior** Clicking outside the dialog or pressing Cancel closes the editor without applying changes, while Save updates the event.
- **Delete flow** The edit dialog includes a Delete event action that removes the event from the calendar.
- **Undo feedback** After deletion, a centered bottom toast appears with an Undo action so the deleted event can be restored.
- **Responsive behavior** The dialog adapts to screen size and device type, using touch-optimized controls such as scrollers on smaller or touch devices.
- **Mobile presentation** On smaller screens, the dialog is shown as a sheet instead of a popover.
- **Day cell states** Hovering a day cell highlights the day number with a gray background, while clicking the empty part of the cell selects the day and highlights the day number with a blue background.

## Best for

- **Monthly event management** Managing create, read, update, and delete workflows in a month-based calendar view.
- **General scheduling** Applications where users need to add, edit, and remove events directly from the calendar.
- **Mixed event types** Calendars that need to handle single-day, multi-day, all-day, and timed events in the same interface.
- **Responsive scheduling UIs** Products that need the same event management flow to work well on desktop and touch devices.

## 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
- [`labels`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-labels) - option
- [`cssClass`](https://mobiscroll.com/docs/react/popup/api#opt-cssClass) - option
- [`display`](https://mobiscroll.com/docs/react/popup/api#opt-display) - option
- [`anchor`](https://mobiscroll.com/docs/react/popup/api#opt-anchor) - option
- [`buttons`](https://mobiscroll.com/docs/react/popup/api#opt-buttons) - option
- [`controls`](https://mobiscroll.com/docs/react/datepicker/api#opt-controls) - option
- [`touchUi`](https://mobiscroll.com/docs/react/datepicker/api#opt-touchUi) - option
- [`navigateToEvent`](https://mobiscroll.com/docs/react/eventcalendar/api#method-navigateToEvent) - method
- [`width`](https://mobiscroll.com/docs/react/popup/api#opt-width) - option
- [`fullScreen`](https://mobiscroll.com/docs/react/popup/api#opt-fullScreen) - option
- [`touchUi`](https://mobiscroll.com/docs/react/popup/api#opt-touchUi) - 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
- [`data`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-data) - option
- [`view`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-view) - option
- [`onEventClick`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventClick) - event
- [`onEventCreated`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreated) - event
- [`onEventDeleted`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventDeleted) - event
- [`scrollLock`](https://mobiscroll.com/docs/react/popup/api#opt-scrollLock) - option
- [`headerText`](https://mobiscroll.com/docs/react/popup/api#opt-headerText) - option
- [`responsive`](https://mobiscroll.com/docs/react/popup/api#opt-responsive) - option
- [`isOpen`](https://mobiscroll.com/docs/react/popup/api#opt-isOpen) - option
- [`onClose`](https://mobiscroll.com/docs/react/popup/api#event-onClose) - event
- [`select`](https://mobiscroll.com/docs/react/datepicker/api#opt-select) - option
- [`display`](https://mobiscroll.com/docs/react/datepicker/api#opt-display) - option
- [`startInput`](https://mobiscroll.com/docs/react/datepicker/api#opt-startInput) - option
- [`endInput`](https://mobiscroll.com/docs/react/datepicker/api#opt-endInput) - option
- [`showRangeLabels`](https://mobiscroll.com/docs/react/datepicker/api#opt-showRangeLabels) - option
- [`responsive`](https://mobiscroll.com/docs/react/datepicker/api#opt-responsive) - option
- [`onChange`](https://mobiscroll.com/docs/react/datepicker/api#event-onChange) - event
- [`showArrow`](https://mobiscroll.com/docs/react/popup/api#opt-showArrow) - option
- [`showOverlay`](https://mobiscroll.com/docs/react/popup/api#opt-showOverlay) - option
- [`onClose`](https://mobiscroll.com/docs/react/notifications/snackbar#event-onClose) - event

## Code

### JSX

```jsx
import {
  Button,
  Datepicker,
  Dropdown,
  Eventcalendar,
  Input,
  Popup,
  Segmented,
  SegmentedGroup,
  setOptions,
  Snackbar,
  Switch,
  Textarea,
} from '@mobiscroll/react';
import { useCallback, useMemo, useRef, useState } from 'react';

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

const defaultEvents = [
  {
    id: 1,
    start: '2026-10-08T13:00',
    end: '2026-10-08T13:45',
    title: "Lunch @ Butcher's",
    description: '',
    allDay: false,
    bufferBefore: 15,
    free: true,
    color: '#009788',
  },
  {
    id: 2,
    start: '2026-10-23T15:00',
    end: '2026-10-23T16:00',
    title: 'Conference',
    description: '',
    allDay: false,
    bufferBefore: 30,
    free: false,
    color: '#ff9900',
  },
  {
    id: 3,
    start: '2026-10-22T18:00',
    end: '2026-10-22T22:00',
    title: 'Site Visit',
    description: '',
    allDay: false,
    bufferBefore: 60,
    free: true,
    color: '#3f51b5',
  },
  {
    id: 4,
    start: '2026-10-24T10:30',
    end: '2026-10-24T11:30',
    title: 'Stakeholder mtg.',
    description: '',
    allDay: false,
    free: false,
    color: '#f44437',
  },
];

const colors = ['#ffeb3c', '#ff9900', '#f44437', '#ea1e63', '#9c26b0', '#3f51b5', '', '#009788', '#4baf4f', '#7e5d4e'];

function App() {
  const [myEvents, setMyEvents] = useState(defaultEvents);
  const [editedEvent, setEditedEvent] = useState({});

  const [eventId, setEventId] = useState('');
  const [eventTitle, setEventTitle] = useState('');
  const [eventDescription, setEventDescription] = useState('');
  const [eventAllDay, setEventAllDay] = useState(false);
  const [eventStart, setEventStart] = useState(null);
  const [eventEnd, setEventEnd] = useState(null);
  const [dateStart, startRef] = useState(null);
  const [dateEnd, endRef] = useState(null);
  const [eventBuffer, setEventBuffer] = useState(0);
  const [eventColor, setEventColor] = useState('');
  const [eventStatus, setEventStatus] = useState(false);

  const [addEditPopupAnchor, setAddEditPopupAnchor] = useState(null);
  const [addEditPopupOpen, setAddEditPopupOpen] = useState(false);
  const [colorPickerAnchor, setColorPickerAnchor] = useState(null);
  const [colorPickerOpen, setColorPickerOpen] = useState(false);
  const [selectedColor, setSelectedColor] = useState('');
  const [isEdit, setEdit] = useState(false);
  const [isSuccess, setSuccess] = useState(false);
  const [snackbarOpen, setSnackbarOpen] = useState(false);

  const calInst = useRef();
  const colorPickerRef = useRef();

  const applySelectedColor = useCallback((color) => {
    setEventColor(color);
    setColorPickerOpen(false);
  }, []);

  const myView = useMemo(() => ({ calendar: { labels: true } }), []);

  const colorPickerButtons = useMemo(
    () => [
      'cancel',
      {
        text: 'Set',
        keyCode: 'enter',
        handler: () => applySelectedColor(selectedColor),
        cssClass: 'mbsc-popup-button-primary',
      },
    ],
    [applySelectedColor, selectedColor],
  );

  const colorPickerResponsive = useMemo(
    () => ({
      medium: {
        display: 'anchored',
        anchor: colorPickerAnchor,
        buttons: [],
      },
    }),
    [colorPickerAnchor],
  );

  const datepickerControls = useMemo(() => (eventAllDay ? ['date'] : ['datetime']), [eventAllDay]);

  const datepickerResponsive = useMemo(
    () =>
      eventAllDay
        ? {
            medium: { controls: ['calendar'], touchUi: false },
          }
        : {
            medium: { controls: ['calendar', 'time'], touchUi: false },
          },
    [eventAllDay],
  );

  const addEditPopupHeaderText = useMemo(() => (isEdit ? 'Edit event' : 'New Event'), [isEdit]);

  const snackbarButton = useMemo(
    () => ({
      action: () => {
        setMyEvents((prevEvents) => [...prevEvents, editedEvent]);
      },
      text: 'Undo',
    }),
    [editedEvent],
  );

  const eventData = useMemo(
    () => ({
      id: eventId,
      title: eventTitle,
      description: eventDescription,
      allDay: eventAllDay,
      start: eventStart,
      end: eventEnd,
      bufferBefore: eventBuffer,
      color: eventColor,
      free: eventStatus,
    }),
    [eventId, eventTitle, eventDescription, eventAllDay, eventStart, eventEnd, eventBuffer, eventColor, eventStatus],
  );

  const addEditPopupButtons = useMemo(() => {
    if (isEdit) {
      return [
        'cancel',
        {
          text: 'Save',
          keyCode: 'enter',
          cssClass: 'mbsc-popup-button-primary',
          handler: () => {
            const updatedEvent = eventData;
            const index = myEvents.findIndex((x) => x.id === updatedEvent.id);
            const newEventList = [...myEvents];

            // Update event in the list
            newEventList.splice(index, 1, updatedEvent);
            setMyEvents(newEventList);

            calInst.current.navigateToEvent(updatedEvent);
            setAddEditPopupOpen(false);
          },
        },
      ];
    } else {
      return [
        'cancel',
        {
          text: 'Add',
          keyCode: 'enter',
          cssClass: 'mbsc-popup-button-primary',
          handler: () => {
            const newEvent = eventData;

            // Add new event to the list
            setMyEvents([...myEvents, newEvent]);

            setSuccess(true);
            calInst.current.navigateToEvent(newEvent);
            setAddEditPopupOpen(false);
          },
        },
      ];
    }
  }, [isEdit, eventData, myEvents]);

  const addEditPopupResponsive = useMemo(
    () => ({
      medium: {
        display: 'anchored',
        width: 400,
        fullScreen: false,
        touchUi: false,
      },
    }),
    [],
  );

  const handleTitleChange = useCallback((ev) => {
    setEventTitle(ev.target.value);
  }, []);

  const handleDescriptionChange = useCallback((ev) => {
    setEventDescription(ev.target.value);
  }, []);

  const handleAllDayChange = useCallback((ev) => {
    setEventAllDay(ev.target.checked);
  }, []);

  const handleDateChange = useCallback((args) => {
    setEventStart(args.value[0]);
    setEventEnd(args.value[1]);
  }, []);

  const handleBufferChange = useCallback((ev) => {
    setEventBuffer(+ev.target.value);
  }, []);

  const handleStatusChange = useCallback((ev) => {
    setEventStatus(ev.target.value === 'free');
  }, []);

  const fillPopup = useCallback((event) => {
    setEventId(event.id);
    setEventTitle(event.title || '');
    setEventDescription(event.description || '');
    setEventAllDay(event.allDay);
    setEventStart(event.start);
    setEventEnd(event.end);
    setEventBuffer(event.bufferBefore || 0);
    setEventColor(event.color || '');
    setEventStatus(event.free);
  }, []);

  const createEditPopup = useCallback(
    (event, target) => {
      setEdit(true);
      setEditedEvent(event);
      setAddEditPopupAnchor(target);
      fillPopup(event);
      setAddEditPopupOpen(true);
    },
    [fillPopup],
  );

  const createAddPopup = useCallback(
    (event, target) => {
      setSuccess(false);
      setEdit(false);
      setEditedEvent(event);
      setAddEditPopupAnchor(target);
      fillPopup({ ...event, allDay: false });
      setAddEditPopupOpen(true);
    },
    [fillPopup],
  );

  const handleEventClick = useCallback(
    (args) => {
      createEditPopup(args.event, args.domEvent.currentTarget);
    },
    [createEditPopup],
  );

  const handleEventCreated = useCallback(
    (args) => {
      createAddPopup(args.event, args.target);
    },
    [createAddPopup],
  );

  const handleEventDeleted = useCallback(() => {
    setSnackbarOpen(true);
  }, []);

  const handleAddEditPopupClose = useCallback(() => {
    if (!isEdit && !isSuccess) {
      // Refresh the list, if add popup was canceled, to remove the temporary event
      setMyEvents([...myEvents]);
    }
    setAddEditPopupOpen(false);
  }, [isEdit, isSuccess, myEvents]);

  const handleDeleteButtonClick = useCallback(() => {
    const filteredEvents = myEvents.filter((e) => e.id !== editedEvent.id);
    setMyEvents(filteredEvents);
    setAddEditPopupOpen(false);
    setSnackbarOpen(true);
  }, [editedEvent, myEvents]);

  const handleSnackbarClose = useCallback(() => {
    setSnackbarOpen(false);
  }, []);

  const handleEventColorClick = useCallback((args) => {
    setColorPickerAnchor(args.currentTarget);
    setColorPickerOpen(true);
  }, []);

  const handleColorChange = useCallback(
    (args) => {
      const color = args.currentTarget.getAttribute('data-value');
      setEventColor(color);
      setSelectedColor(color);
      if (!colorPickerRef.current.s.buttons.length) {
        applySelectedColor(color);
      }
    },
    [applySelectedColor],
  );

  const handleColorPickerClose = useCallback(() => {
    setColorPickerOpen(false);
  }, []);

  return (
    <>
      <Eventcalendar
        clickToCreate={true}
        dragToCreate={true}
        dragToMove={true}
        dragToResize={true}
        data={myEvents}
        ref={calInst}
        view={myView}
        onEventClick={handleEventClick}
        onEventCreated={handleEventCreated}
        onEventDeleted={handleEventDeleted}
      />

      <Popup
        display="bottom"
        contentPadding={false}
        fullScreen={true}
        scrollLock={false}
        headerText={addEditPopupHeaderText}
        anchor={addEditPopupAnchor}
        buttons={addEditPopupButtons}
        responsive={addEditPopupResponsive}
        isOpen={addEditPopupOpen}
        onClose={handleAddEditPopupClose}
      >
        <div className="mbsc-form-group">
          <Input label="Title" value={eventTitle} onChange={handleTitleChange} />
          <Textarea label="Description" value={eventDescription} onChange={handleDescriptionChange} />
        </div>

        <div className="mbsc-form-group">
          <div>
            <Switch label="All-day" checked={eventAllDay} onChange={handleAllDayChange} />

            <Datepicker
              select="range"
              display="anchored"
              controls={datepickerControls}
              touchUi={true}
              startInput={dateStart}
              endInput={dateEnd}
              showRangeLabels={false}
              responsive={datepickerResponsive}
              onChange={handleDateChange}
              value={[eventStart, eventEnd]}
            />

            <Input ref={startRef} label="Starts" />
            <Input ref={endRef} label="Ends" />

            {!eventAllDay && (
              <Dropdown label="Travel time" value={eventBuffer} onChange={handleBufferChange}>
                <option value="0">None</option>
                <option value="5">5 minutes</option>
                <option value="15">15 minutes</option>
                <option value="30">30 minutes</option>
                <option value="60">1 hour</option>
                <option value="90">1.5 hours</option>
                <option value="120">2 hours</option>
              </Dropdown>
            )}
          </div>

          <div onClick={handleEventColorClick} className="mbsc-flex mds-crud-event-color-cont">
            <div className="mbsc-flex-1-0">Color</div>
            <div className="mds-crud-selected-event-color" style={{ background: eventColor }} />
          </div>

          <SegmentedGroup onChange={handleStatusChange}>
            <Segmented value="busy" checked={!eventStatus}>
              Show as busy
            </Segmented>
            <Segmented value="free" checked={eventStatus}>
              Show as free
            </Segmented>
          </SegmentedGroup>

          {isEdit && (
            <div className="mbsc-button-group">
              <Button className="mbsc-button-block" color="danger" variant="outline" onClick={handleDeleteButtonClick}>
                Delete event
              </Button>
            </div>
          )}
        </div>
      </Popup>

      <Popup
        display="bottom"
        contentPadding={false}
        showArrow={false}
        showOverlay={false}
        ref={colorPickerRef}
        anchor={colorPickerAnchor}
        isOpen={colorPickerOpen}
        buttons={colorPickerButtons}
        responsive={colorPickerResponsive}
        onClose={handleColorPickerClose}
      >
        <div className="mbsc-flex mds-crud-color-row">
          {colors.slice(0, 5).map((color) => (
            <div
              key={color}
              onClick={handleColorChange}
              className={`mds-crud-color-value ${eventColor === color ? 'mds-crud-color-value-selected' : ''}`}
              data-value={color}
            >
              <div className="mds-crud-color mbsc-icon mbsc-font-icon mbsc-icon-material-check" style={{ background: color }} />
            </div>
          ))}
        </div>

        <div className="mbsc-flex mds-crud-color-row">
          {colors.slice(5).map((color) => (
            <div
              key={color}
              onClick={handleColorChange}
              className={`mds-crud-color-value ${eventColor === color ? 'mds-crud-color-value-selected' : ''}`}
              data-value={color}
            >
              <div className="mds-crud-color mbsc-icon mbsc-font-icon mbsc-icon-material-check" style={{ background: color }} />
            </div>
          ))}
        </div>
      </Popup>

      <Snackbar isOpen={snackbarOpen} message="Event deleted" button={snackbarButton} onClose={handleSnackbarClose} />
    </>
  );
}

export default App;
```

### TSX

```tsx
import {
  Button,
  Datepicker,
  Dropdown,
  Eventcalendar,
  Input,
  MbscCalendarEvent,
  MbscDatepickerChangeEvent,
  MbscDatepickerControl,
  MbscDatepickerOptions,
  MbscDateType,
  MbscEventcalendarView,
  MbscEventClickEvent,
  MbscEventCreatedEvent,
  MbscPopupButton,
  MbscPopupOptions,
  MbscResponsiveOptions,
  Popup,
  Segmented,
  SegmentedGroup,
  setOptions,
  Snackbar,
  Switch,
  Textarea,
} from '@mobiscroll/react';
import { ChangeEvent, FC, MouseEvent, useCallback, useMemo, useRef, useState } from 'react';

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

const defaultEvents: MbscCalendarEvent[] = [
  {
    id: 1,
    start: '2026-10-08T13:00',
    end: '2026-10-08T13:45',
    title: "Lunch @ Butcher's",
    description: '',
    allDay: false,
    bufferBefore: 15,
    free: true,
    color: '#009788',
  },
  {
    id: 2,
    start: '2026-10-23T15:00',
    end: '2026-10-23T16:00',
    title: 'Conference',
    description: '',
    allDay: false,
    bufferBefore: 30,
    free: false,
    color: '#ff9900',
  },
  {
    id: 3,
    start: '2026-10-22T18:00',
    end: '2026-10-22T22:00',
    title: 'Site Visit',
    description: '',
    allDay: false,
    bufferBefore: 60,
    free: true,
    color: '#3f51b5',
  },
  {
    id: 4,
    start: '2026-10-24T10:30',
    end: '2026-10-24T11:30',
    title: 'Stakeholder mtg.',
    description: '',
    allDay: false,
    free: false,
    color: '#f44437',
  },
];

const colors = ['#ffeb3c', '#ff9900', '#f44437', '#ea1e63', '#9c26b0', '#3f51b5', '', '#009788', '#4baf4f', '#7e5d4e'];

const App: FC = () => {
  const [myEvents, setMyEvents] = useState<MbscCalendarEvent[]>(defaultEvents);
  const [editedEvent, setEditedEvent] = useState<MbscCalendarEvent | null>(null);

  const [eventId, setEventId] = useState<string | number | null>(null);
  const [eventTitle, setEventTitle] = useState<string>('');
  const [eventDescription, setEventDescription] = useState<string>('');
  const [eventAllDay, setEventAllDay] = useState<boolean>(false);
  const [eventStart, setEventStart] = useState<MbscDateType | null>(null);
  const [eventEnd, setEventEnd] = useState<MbscDateType | null>(null);
  const [dateStart, startRef] = useState<MbscDateType | null>(null);
  const [dateEnd, endRef] = useState<MbscDateType | null>(null);
  const [eventBuffer, setEventBuffer] = useState<number>(0);
  const [eventColor, setEventColor] = useState<string>('');
  const [eventStatus, setEventStatus] = useState<boolean>(false);

  const [addEditPopupAnchor, setAddEditPopupAnchor] = useState<HTMLElement>();
  const [addEditPopupOpen, setAddEditPopupOpen] = useState<boolean>(false);
  const [colorPickerAnchor, setColorPickerAnchor] = useState<HTMLElement>();
  const [colorPickerOpen, setColorPickerOpen] = useState<boolean>(false);
  const [selectedColor, setSelectedColor] = useState<string>('');
  const [isEdit, setEdit] = useState<boolean>(false);
  const [isSuccess, setSuccess] = useState<boolean>(false);
  const [snackbarOpen, setSnackbarOpen] = useState<boolean>(false);

  const calInst = useRef<Eventcalendar | null>(null);
  const colorPickerRef = useRef<Popup | null>(null);

  const applySelectedColor = useCallback((color: string) => {
    setEventColor(color);
    setColorPickerOpen(false);
  }, []);

  const myView: MbscEventcalendarView = useMemo(() => ({ calendar: { labels: true } }), []);

  const colorPickerButtons = useMemo<(string | MbscPopupButton)[]>(
    () => [
      'cancel',
      {
        text: 'Set',
        keyCode: 'enter',
        handler: () => applySelectedColor(selectedColor),
        cssClass: 'mbsc-popup-button-primary',
      },
    ],
    [applySelectedColor, selectedColor],
  );

  const colorPickerResponsive: MbscResponsiveOptions<MbscPopupOptions> = useMemo(
    () => ({
      medium: {
        display: 'anchored',
        anchor: colorPickerAnchor,
        buttons: [],
      },
    }),
    [colorPickerAnchor],
  );

  const datepickerControls = useMemo(() => (eventAllDay ? ['date'] : ['datetime']) as MbscDatepickerControl[], [eventAllDay]);

  const datepickerResponsive: MbscResponsiveOptions<MbscDatepickerOptions> = useMemo(
    () =>
      eventAllDay
        ? {
            medium: { controls: ['calendar'], touchUi: false },
          }
        : {
            medium: { controls: ['calendar', 'time'], touchUi: false },
          },
    [eventAllDay],
  );

  const addEditPopupHeaderText = useMemo<string>(() => (isEdit ? 'Edit event' : 'New Event'), [isEdit]);

  const snackbarButton = useMemo(
    () => ({
      action: () => {
        setMyEvents((prevEvents) => [...prevEvents, editedEvent!]);
      },
      text: 'Undo',
    }),
    [editedEvent],
  );

  const eventData = useMemo<MbscCalendarEvent>(
    () => ({
      id: eventId!,
      title: eventTitle,
      description: eventDescription,
      allDay: eventAllDay,
      start: eventStart!,
      end: eventEnd!,
      bufferBefore: eventBuffer,
      color: eventColor,
      free: eventStatus,
    }),
    [eventId, eventTitle, eventDescription, eventAllDay, eventStart, eventEnd, eventBuffer, eventColor, eventStatus],
  );

  const addEditPopupButtons = useMemo<(string | MbscPopupButton)[]>(() => {
    if (isEdit) {
      return [
        'cancel',
        {
          text: 'Save',
          keyCode: 'enter',
          cssClass: 'mbsc-popup-button-primary',
          handler: () => {
            const updatedEvent: MbscCalendarEvent = eventData;
            const index = myEvents.findIndex((x) => x.id === updatedEvent.id);
            const newEventList = [...myEvents];

            // Update event in the list
            newEventList.splice(index, 1, updatedEvent);
            setMyEvents(newEventList);

            calInst.current!.navigateToEvent(updatedEvent);
            setAddEditPopupOpen(false);
          },
        },
      ];
    } else {
      return [
        'cancel',
        {
          text: 'Add',
          keyCode: 'enter',
          cssClass: 'mbsc-popup-button-primary',
          handler: () => {
            const newEvent: MbscCalendarEvent = eventData;

            // Add new event to the list
            setMyEvents([...myEvents, newEvent]);

            setSuccess(true);
            calInst.current!.navigateToEvent(newEvent);
            setAddEditPopupOpen(false);
          },
        },
      ];
    }
  }, [isEdit, eventData, myEvents]);

  const addEditPopupResponsive = useMemo<MbscResponsiveOptions<MbscPopupOptions>>(
    () => ({
      medium: {
        display: 'anchored',
        width: 400,
        fullScreen: false,
        touchUi: false,
      },
    }),
    [],
  );

  const handleTitleChange = useCallback((ev: ChangeEvent<HTMLInputElement>) => {
    setEventTitle(ev.target.value);
  }, []);

  const handleDescriptionChange = useCallback((ev: ChangeEvent<HTMLTextAreaElement>) => {
    setEventDescription(ev.target.value);
  }, []);

  const handleAllDayChange = useCallback((ev: ChangeEvent<HTMLInputElement>) => {
    setEventAllDay(ev.target.checked);
  }, []);

  const handleDateChange = useCallback((args: MbscDatepickerChangeEvent) => {
    const value = args.value as MbscDateType[];
    setEventStart(value[0]);
    setEventEnd(value[1]);
  }, []);

  const handleBufferChange = useCallback((ev: ChangeEvent<HTMLInputElement>) => {
    setEventBuffer(+ev.target.value);
  }, []);

  const handleStatusChange = useCallback((ev: ChangeEvent<HTMLInputElement>) => {
    setEventStatus(ev.target.value === 'free');
  }, []);

  const fillPopup = useCallback((event: MbscCalendarEvent) => {
    setEventId(event.id!);
    setEventTitle(event.title || '');
    setEventDescription(event.description || '');
    setEventAllDay(event.allDay!);
    setEventStart(event.start!);
    setEventEnd(event.end!);
    setEventBuffer(event.bufferBefore || 0);
    setEventColor(event.color || '');
    setEventStatus(event.free);
  }, []);

  const createEditPopup = useCallback(
    (event: MbscCalendarEvent, target: HTMLElement) => {
      setEdit(true);
      setEditedEvent(event);
      setAddEditPopupAnchor(target);
      fillPopup(event);
      setAddEditPopupOpen(true);
    },
    [fillPopup],
  );

  const createAddPopup = useCallback(
    (event: MbscCalendarEvent, target: HTMLElement) => {
      setSuccess(false);
      setEdit(false);
      setEditedEvent(event);
      setAddEditPopupAnchor(target);
      fillPopup({ ...event, allDay: false });
      setAddEditPopupOpen(true);
    },
    [fillPopup],
  );

  const handleEventClick = useCallback(
    (args: MbscEventClickEvent) => {
      createEditPopup(args.event, args.domEvent.currentTarget);
    },
    [createEditPopup],
  );

  const handleEventCreated = useCallback(
    (args: MbscEventCreatedEvent) => {
      createAddPopup(args.event, args.target!);
    },
    [createAddPopup],
  );

  const handleEventDeleted = useCallback(() => {
    setSnackbarOpen(true);
  }, []);

  const handleAddEditPopupClose = useCallback(() => {
    if (!isEdit && !isSuccess) {
      // Refresh the list, if add popup was canceled, to remove the temporary event
      setMyEvents([...myEvents]);
    }
    setAddEditPopupOpen(false);
  }, [isEdit, isSuccess, myEvents]);

  const handleDeleteButtonClick = useCallback(() => {
    const filteredEvents = myEvents.filter((e) => e.id !== editedEvent!.id);
    setMyEvents(filteredEvents);
    setAddEditPopupOpen(false);
    setSnackbarOpen(true);
  }, [editedEvent, myEvents]);

  const handleSnackbarClose = useCallback(() => {
    setSnackbarOpen(false);
  }, []);

  const handleEventColorClick = useCallback((ev: MouseEvent<HTMLDivElement>) => {
    setColorPickerAnchor(ev.currentTarget);
    setColorPickerOpen(true);
  }, []);

  const handleColorChange = useCallback(
    (ev: MouseEvent<HTMLDivElement>) => {
      const color = ev.currentTarget.getAttribute('data-value') || '';
      setEventColor(color);
      setSelectedColor(color);
      if (!colorPickerRef.current!.s.buttons!.length) {
        applySelectedColor(color);
      }
    },
    [applySelectedColor],
  );

  const handleColorPickerClose = useCallback(() => {
    setColorPickerOpen(false);
  }, []);

  return (
    <>
      <Eventcalendar
        clickToCreate={true}
        dragToCreate={true}
        dragToMove={true}
        dragToResize={true}
        data={myEvents}
        ref={calInst}
        view={myView}
        onEventClick={handleEventClick}
        onEventCreated={handleEventCreated}
        onEventDeleted={handleEventDeleted}
      />

      <Popup
        display="bottom"
        contentPadding={false}
        fullScreen={true}
        scrollLock={false}
        headerText={addEditPopupHeaderText}
        anchor={addEditPopupAnchor}
        buttons={addEditPopupButtons}
        responsive={addEditPopupResponsive}
        isOpen={addEditPopupOpen}
        onClose={handleAddEditPopupClose}
      >
        <div className="mbsc-form-group">
          <Input label="Title" value={eventTitle} onChange={handleTitleChange} />
          <Textarea label="Description" value={eventDescription} onChange={handleDescriptionChange} />
        </div>

        <div className="mbsc-form-group">
          <div>
            <Switch label="All-day" checked={eventAllDay} onChange={handleAllDayChange} />

            <Datepicker
              select="range"
              display="anchored"
              controls={datepickerControls}
              touchUi={true}
              startInput={dateStart}
              endInput={dateEnd}
              showRangeLabels={false}
              responsive={datepickerResponsive}
              onChange={handleDateChange}
              value={[eventStart, eventEnd]}
            />

            <Input ref={startRef} label="Starts" />
            <Input ref={endRef} label="Ends" />

            {!eventAllDay && (
              <Dropdown label="Travel time" value={String(eventBuffer)} onChange={handleBufferChange}>
                <option value="0">None</option>
                <option value="5">5 minutes</option>
                <option value="15">15 minutes</option>
                <option value="30">30 minutes</option>
                <option value="60">1 hour</option>
                <option value="90">1.5 hours</option>
                <option value="120">2 hours</option>
              </Dropdown>
            )}
          </div>

          <div onClick={handleEventColorClick} className="mbsc-flex mds-crud-event-color-cont">
            <div className="mbsc-flex-1-0">Color</div>
            <div className="mds-crud-selected-event-color" style={{ background: eventColor }} />
          </div>

          <SegmentedGroup onChange={handleStatusChange}>
            <Segmented value="busy" checked={!eventStatus}>
              Show as busy
            </Segmented>
            <Segmented value="free" checked={eventStatus}>
              Show as free
            </Segmented>
          </SegmentedGroup>

          {isEdit && (
            <div className="mbsc-button-group">
              <Button className="mbsc-button-block" color="danger" variant="outline" onClick={handleDeleteButtonClick}>
                Delete event
              </Button>
            </div>
          )}
        </div>
      </Popup>

      <Popup
        display="bottom"
        contentPadding={false}
        showArrow={false}
        showOverlay={false}
        ref={colorPickerRef}
        anchor={colorPickerAnchor}
        isOpen={colorPickerOpen}
        buttons={colorPickerButtons}
        responsive={colorPickerResponsive}
        onClose={handleColorPickerClose}
      >
        <div className="mbsc-flex mds-crud-color-row">
          {colors.slice(0, 5).map((color) => (
            <div
              key={color}
              onClick={handleColorChange}
              className={`mds-crud-color-value ${eventColor === color ? 'mds-crud-color-value-selected' : ''}`}
              data-value={color}
            >
              <div className="mds-crud-color mbsc-icon mbsc-font-icon mbsc-icon-material-check" style={{ background: color }} />
            </div>
          ))}
        </div>

        <div className="mbsc-flex mds-crud-color-row">
          {colors.slice(5).map((color) => (
            <div
              key={color}
              onClick={handleColorChange}
              className={`mds-crud-color-value ${eventColor === color ? 'mds-crud-color-value-selected' : ''}`}
              data-value={color}
            >
              <div className="mds-crud-color mbsc-icon mbsc-font-icon mbsc-icon-material-check" style={{ background: color }} />
            </div>
          ))}
        </div>
      </Popup>

      <Snackbar isOpen={snackbarOpen} message="Event deleted" button={snackbarButton} onClose={handleSnackbarClose} />
    </>
  );
};
export default App;
```

### CSS

```css
.mds-crud-event-color-cont {
  margin: 16px;
  align-items: center;
  cursor: pointer;
}

.mds-crud-selected-event-color {
  width: 30px;
  height: 30px;
  border-radius: 15px;
  margin-right: 10px;
  margin-left: 240px;
  background: #5ac8fa;
}

.mds-crud-color-row {
  justify-content: center;
  margin: 5px;
}

.mds-crud-color {
  position: relative;
  min-width: 46px;
  min-height: 46px;
  margin: 2px;
  cursor: pointer;
  border-radius: 23px;
  background: #5ac8fa;
}

.mds-crud-color:before {
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -10px;
  margin-left: -10px;
  color: #f7f7f7;
  font-size: 20px;
  text-shadow: 0 0 3px #000;
  display: none;
}

.mds-crud-color-value {
  padding: 3px;
  margin: 2px;
}

.mds-crud-color-value.mds-crud-color-value-selected,
.mds-crud-color-value:hover {
  box-shadow: inset 0 0 0 3px #007bff;
  border-radius: 48px;
}

.mds-crud-color-value.mds-crud-color-value-selected .mds-crud-color:before {
  display: block;
}
```

## Implementation notes

- Use `view: { calendar: { labels: true } }`. Enable all four interaction modes: `clickToCreate` (React/Vue/Angular: `true`; JS/jQuery: `'double'`), `dragToCreate: true`, `dragToMove: true`, `dragToResize: true`.
- Start with 4 sample events using `dyndatetime` offsets. Each event carries: `id`, `title`, `description`, `allDay`, `start`, `end`, `bufferBefore`, `free`, and `color`.
- Handle three calendar events: `onEventClick` → opens the edit popup; `onEventCreated` → opens the add popup (the event is already temporarily placed on the calendar); `onEventDeleted` → shows a snackbar ("Event deleted") with an "Undo" button that adds the event back. Angular: use the `Notifications` service for the snackbar.
- Maintain an `isEdit` flag and an `editedEvent` reference. A `fillPopup(event)` helper populates all form fields from the event object.
- **Edit mode**: set `isEdit = true`, store `editedEvent`, set popup anchor to the clicked element, call `fillPopup`, open with "Edit event" header and a "Save" button.
- **Add mode**: set `isEdit = false`, set `isSuccess = false`, call `fillPopup`, open with "New Event" header and an "Add" button.
- **On popup close (add mode, not confirmed)**: if `isEdit` is false and `isSuccess` is false, refresh the events array (spread trick) to strip the temporary event; for the imperative API, call `calendar.removeEvent(event)` in the popup's `onClose` handler.
- **"Add" button**: build the event from form state, append to the events array, set `isSuccess = true`, call `inst.navigateToEvent(newEvent)`, close popup.
- **"Save" button**: build updated event from form state, find by `id` and splice in place, call `inst.navigateToEvent(updatedEvent)`, close popup.
- **Popup form fields**: **Title** (`Input`), **Description** (`Textarea`), **All-day** (`Switch` - when on, switches the date picker to date-only and removes the Travel time row), **Start/End** (a `Datepicker` in range mode with `startInput`/`endInput`; controls are `['datetime']` normally, `['date']` for all-day), **Travel time** (`Dropdown` with None/5/15/30 min/1 h/1.5 h/2 h options mapped to `bufferBefore` in minutes - hidden when all-day), **Color** (a clickable row opening a color picker popup with 10 swatches), **Show as busy / free** (`SegmentedGroup` mapping to the `free` boolean), and a **Delete event** button (visible in edit mode only).
- The color picker is a separate `Popup` with 10 color swatches in 2 rows of 5: `#ffeb3c`, `#ff9900`, `#f44437`, `#ea1e63`, `#9c26b0` / `#3f51b5`, empty string (no color), `#009788`, `#4baf4f`, `#7e5d4e`. On desktop (`display: 'anchored'`, no buttons): clicking applies and closes immediately. On mobile: bottom-sheet with "Set"/"Cancel". The active swatch is highlighted.
- **Delete from popup**: filter the event from the array by `id`, close the popup, show a snackbar with "Undo".

## 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

- Next: [Recurring Events Editor](https://demo.mobiscroll.com/react/eventcalendar/recurring-event-add-edit-dialog.md) - Add recurring event management to a React event calendar — use preset daily, weekly, monthly, and yearly rules or a custom recurrence editor.
