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

# Create, Edit and Delete Events - React Scheduler demo

Implement full event CRUD in a React Scheduler — drag or click to create events, confirm via a custom dialog, then drag to move, resize, or delete. For React and Next.JS.

- [Live demo](https://demo.mobiscroll.com/react/scheduler/create-read-update-delete-CRUD)
- [View React JS code on GitHub](https://github.com/acidb/mobiscroll-demos-react/tree/main/src/demos/eventcalendar/scheduler/create-read-update-delete-CRUD)
- [View React TS code on GitHub](https://github.com/acidb/mobiscroll-demos-react-ts/tree/main/src/demos/eventcalendar/scheduler/create-read-update-delete-CRUD)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/scheduler/create-read-update-delete-CRUD.md), [jQuery](https://demo.mobiscroll.com/jquery/scheduler/create-read-update-delete-CRUD.md), [Angular](https://demo.mobiscroll.com/angular/scheduler/create-read-update-delete-CRUD.md), [Vue](https://demo.mobiscroll.com/vue/scheduler/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 this example →](https://demo.mobiscroll.com/react/scheduler/recurring-event-add-edit-dialog)

## 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.
- **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 creation** Double-clicking the time grid or clicking and dragging on it creates a new event.
- **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 scheduler.
- **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 scheduler.
- **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.

## Best for

- **Weekly event management** Managing create, read, update, and delete workflows in a week-based scheduler view.
- **General scheduling** Applications where users need to add, edit, and remove events directly from the scheduler.
- **Mixed event types** Schedulers 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
- [`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-29T15:00',
    end: '2026-10-29T16:00',
    title: 'Conference',
    description: '',
    allDay: false,
    bufferBefore: 30,
    free: false,
    color: '#ff9900',
  },
  {
    id: 3,
    start: '2026-10-28T18:00',
    end: '2026-10-28T22:00',
    title: 'Site Visit',
    description: '',
    allDay: false,
    bufferBefore: 60,
    free: true,
    color: '#3f51b5',
  },
  {
    id: 4,
    start: '2026-10-30T10:30',
    end: '2026-10-30T11: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(() => ({ scheduler: { type: 'week' } }), []);

  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);
      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-29T15:00',
    end: '2026-10-29T16:00',
    title: 'Conference',
    description: '',
    allDay: false,
    bufferBefore: 30,
    free: false,
    color: '#ff9900',
  },
  {
    id: 3,
    start: '2026-10-28T18:00',
    end: '2026-10-28T22:00',
    title: 'Site Visit',
    description: '',
    allDay: false,
    bufferBefore: 60,
    free: true,
    color: '#3f51b5',
  },
  {
    id: 4,
    start: '2026-10-30T10:30',
    end: '2026-10-30T11: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(() => ({ scheduler: { type: 'week' } }), []);

  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);
      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: { scheduler: { type: 'week' } }`. 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.
- 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. On desktop: 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: [Bulk Event Actions](https://demo.mobiscroll.com/react/scheduler/event-bulk-actions-edit-delete-update.md) - Add bulk event actions to React Scheduler — select multiple events via shortcuts or select-all, apply bulk actions, and use right-click for per-event actions.
