---
title: "React Event calendar Recurring Events Editor Example | Mobiscroll"
component: "Event calendar"
framework: "React"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/react/eventcalendar/recurring-event-add-edit-dialog"
---

# Recurring Events Editor - React Event calendar demo

Add recurring event management to a React event calendar — use preset daily, weekly, monthly, and yearly rules or a custom recurrence editor. For React and Next.JS.

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

## Description

As seen in the [previous example](https://demo.mobiscroll.com/react/eventcalendar/create-read-update-delete-CRUD), an add/edit form can be created with custom and base event fields. While the CRUD example features delete with undo, this example shows off how to build a fully customizable recurrence editor right in the add/edit screen.

Above some common presets, like **Daily**, **Weekly on X day**, **Monthly on Xth**, **Annually on the same day**, **Every Weekday** a fully custom recurrence editor is implemented.

Copy & paste or delve into the code and understand how to generate recurrence objects or RRULES as seen in this [live rule builder](https://demo.mobiscroll.com/react/eventcalendar/recurring-events).

## What this demo shows

- A desktop month view event calendar with recurring events already rendered across the month.
- **Calendar header** The header shows the current month and year on the left, and blue month navigation arrows with a `Today` button (for jumping back to the current date) between them on the right. 
- **Event creation** Users can create a new event by clicking and dragging across calendar cells or by double-clicking a day cell.
- **Create dialog** Creating an event opens an event editor with fields for title, description, all-day, start date, end date, and recurrence.
- **Date selection** The start and end fields open a date picker with range selection.
- **Recurring presets** The Repeats field opens a dropdown with preset options including Does not repeat, Daily, Weekly, Monthly, Yearly, and Custom.
- **Custom recurrence** Choosing Custom expands the editor with additional controls for defining a custom recurring rule.
- **Event interaction** Hovering over an event label highlights it. This action shows resize handles on both sides, indicating that the event can be resized by clicking and dragging.
- **Event editing** Clicking an existing event opens the same dialog in edit mode with the current event values prefilled.
- **Save and cancel flow** Clicking outside the dialog or pressing Cancel closes the editor without applying changes, while Save applies the changes to the event.
- **Delete flow** In edit mode, the dialog includes a Delete event action for removing the selected event.
- **Recurring delete options** When deleting a recurring event, a confirmation popup appears with options for This event only, This and following events, and All events, plus Cancel and Ok actions.
- **Responsive editor** The event editor adapts to screen size and device type, using touch-friendly controls such as scrollers on smaller or touch devices.
- **Cell hover and selection** Hovering a day cell highlights the day number with a gray background, while clicking an empty part of a day cell selects that day and highlights the day number with a blue background.

## Best for

- **Recurring event management** Apps that need users to create, edit, and delete repeating events directly from a calendar view.
- **Exception handling** Scenarios where users may need to delete a single occurrence, all following occurrences, or the entire recurring series.
- **Business and operational scheduling** Internal tools or customer-facing products for planning appointments, classes, shifts, programs, or other repeating activities.

## 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
- [`display`](https://mobiscroll.com/docs/react/popup/api#opt-display) - option
- [`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
- [`touchUi`](https://mobiscroll.com/docs/react/select/api#opt-touchUi) - option
- [`controls`](https://mobiscroll.com/docs/react/datepicker/api#opt-controls) - option
- [`touchUi`](https://mobiscroll.com/docs/react/datepicker/api#opt-touchUi) - option
- [`cssClass`](https://mobiscroll.com/docs/react/popup/api#opt-cssClass) - option
- [`view`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-view) - option
- [`data`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-data) - 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
- [`selectedDate`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-selectedDate) - option
- [`onSelectedDateChange`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onSelectedDateChange) - event
- [`onEventClick`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventClick) - event
- [`onEventUpdate`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventUpdate) - event
- [`onEventCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreate) - event
- [`onEventCreated`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreated) - event
- [`onEventDeleted`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventDeleted) - event
- [`onEventUpdated`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventUpdated) - event
- [`scrollLock`](https://mobiscroll.com/docs/react/popup/api#opt-scrollLock) - option
- [`height`](https://mobiscroll.com/docs/react/popup/api#opt-height) - option
- [`headerText`](https://mobiscroll.com/docs/react/popup/api#opt-headerText) - option
- [`anchor`](https://mobiscroll.com/docs/react/popup/api#opt-anchor) - option
- [`buttons`](https://mobiscroll.com/docs/react/popup/api#opt-buttons) - option
- [`isOpen`](https://mobiscroll.com/docs/react/popup/api#opt-isOpen) - option
- [`onClose`](https://mobiscroll.com/docs/react/popup/api#event-onClose) - event
- [`responsive`](https://mobiscroll.com/docs/react/popup/api#opt-responsive) - option
- [`select`](https://mobiscroll.com/docs/react/datepicker/api#opt-select) - 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
- [`data`](https://mobiscroll.com/docs/react/select/api#opt-data) - option
- [`label`](https://mobiscroll.com/docs/react/select/api#opt-label) - option
- [`responsive`](https://mobiscroll.com/docs/react/select/api#opt-responsive) - option
- [`onChange`](https://mobiscroll.com/docs/react/select/api#event-onChange) - event
- [`inputStyle`](https://mobiscroll.com/docs/react/select/api#opt-inputStyle) - option
- [`position`](https://mobiscroll.com/docs/react/forms/radio#opt-position) - option
- [`inputStyle`](https://mobiscroll.com/docs/react/datepicker/api#opt-inputStyle) - option
- [`display`](https://mobiscroll.com/docs/react/datepicker/api#opt-display) - option
- [`dateFormat`](https://mobiscroll.com/docs/react/datepicker/api#localization-dateFormat) - option
- [`returnFormat`](https://mobiscroll.com/docs/react/datepicker/api#opt-returnFormat) - option
- [`onOpen`](https://mobiscroll.com/docs/react/datepicker/api#event-onOpen) - event

## Code

### JSX

```jsx
import {
  Button,
  Datepicker,
  Eventcalendar,
  formatDate,
  Input,
  Popup,
  Radio,
  RadioGroup,
  Segmented,
  SegmentedGroup,
  Select,
  setOptions,
  Switch,
  Textarea,
  updateRecurringEvent,
} from '@mobiscroll/react';
import { useCallback, useMemo, useState } from 'react';

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

const defaultEvents = [
  {
    id: 1,
    start: '2026-10-21T00:00',
    end: '2026-10-24T00:00',
    title: 'Alice OFF',
    allDay: true,
    color: '#67ab0d',
  },
  {
    id: 2,
    start: '18:00',
    end: '20:00',
    recurring: {
      repeat: 'weekly',
      weekDays: 'MO',
    },
    title: 'Football training',
    allDay: false,
    color: '#fd966a',
  },
  {
    id: 3,
    start: '17:00',
    end: '17:30',
    recurring: {
      repeat: 'monthly',
      day: 1,
    },
    title: 'Rent payment',
    allDay: false,
    color: '#3ea39e',
  },
  {
    id: 4,
    recurring: {
      repeat: 'yearly',
      day: 6,
      month: 5,
    },
    title: 'Dexter BD',
    allDay: true,
    color: '#d00f0f',
  },
];

const myView = {
  calendar: { labels: true },
};

const responsivePopup = {
  medium: {
    display: 'anchored',
    width: 510,
    fullScreen: false,
    touchUi: false,
  },
};

const responsiveSelect = {
  xsmall: {
    touchUi: true,
  },
  small: {
    touchUi: false,
  },
};

const days = [
  { name: 'Sunday', short: 'SU', checked: true },
  { name: 'Monday', short: 'MO', checked: false },
  { name: 'Tuesday', short: 'TU', checked: false },
  { name: 'Wednesday', short: 'WE', checked: false },
  { name: 'Thursday', short: 'TH', checked: false },
  { name: 'Friday', short: 'FR', checked: false },
  { name: 'Saturday', short: 'SA', checked: false },
];

const months = [
  { value: 1, text: 'January' },
  { value: 2, text: 'February' },
  { value: 3, text: 'March' },
  { value: 4, text: 'April' },
  { value: 5, text: 'May' },
  { value: 6, text: 'June' },
  { value: 7, text: 'July' },
  { value: 8, text: 'August' },
  { value: 9, text: 'September' },
  { value: 10, text: 'October' },
  { value: 11, text: 'November' },
  { value: 12, text: 'December' },
];

const ordinalList = { 1: 'first', 2: 'second', 3: 'third', 4: 'fourth' };

function getWeekDayNum(date) {
  const year = date.getFullYear();
  const month = date.getMonth();
  const firstDayOfMonth = new Date(year, month, 1);
  const lastDayOfMonth = new Date(year, month + 1, 0);
  let count = 0;

  for (let d = firstDayOfMonth; d < lastDayOfMonth; d.setDate(d.getDate() + 7)) {
    if (date >= d) {
      count++;
    } else {
      break;
    }
  }

  return Math.max(1, count);
}

function getMonthDays(month) {
  const day30 = [2, 4, 6, 9, 11];
  const newValues = [];
  for (let i = 1; i <= 31; i++) {
    if (!(i === 31 && day30.includes(month)) && !(i === 30 && month === 2)) {
      newValues.push(i);
    }
  }
  return newValues;
}

function App() {
  const [myEvents, setMyEvents] = useState(defaultEvents);
  const [tempEvent, setTempEvent] = useState(null);
  const [isOpen, setOpen] = useState(false);
  const [isEdit, setEdit] = useState(false);
  const [anchor, setAnchor] = useState(null);
  const [start, startRef] = useState(null);
  const [end, endRef] = useState(null);
  const [popupEventTitle, setTitle] = useState('');
  const [popupEventDescription, setDescription] = useState('');
  const [popupEventAllDay, setAllDay] = useState(true);
  const [popupEventDate, setDate] = useState([]);
  const [mySelectedDate, setSelectedDate] = useState(new Date());

  // Recurring editor data
  const [repeatData, setRepeatData] = useState([
    { value: 'norepeat', text: 'Does not repeat' },
    { value: 'daily', text: 'Daily' },
    { value: 'weekly', text: 'Weekly' },
    { value: 'monthly', text: 'Monthly' },
    { value: 'monthly-pos', text: 'Monthly' },
    { value: 'yearly', text: 'Yearly' },
    { value: 'yearly-pos', text: 'Yearly' },
    { value: 'weekday', text: 'Every weekday (Monday to Friday)' },
    { value: 'custom', text: 'Custom' },
  ]);
  const [selectedRepeat, setSelectedRepeat] = useState('norepeat');
  const [repeatType, setRepeatType] = useState('daily');
  const [repeatNr, setRepeatNr] = useState(1);
  const [condition, setCondition] = useState('never');
  const [untilDate, setUntilDate] = useState();
  const [occurrences, setOccurrences] = useState(10);
  const [selectedMonth, setMonth] = useState(1);
  const [monthlyDays, setMonthlyDays] = useState(() => getMonthDays(1));
  const [monthlyDay, setMonthlyDay] = useState(1);
  const [yearlyDays, setYearlyDays] = useState(() => getMonthDays(1));
  const [yearlyDay, setYearlyDay] = useState(1);
  const [weekDays, setWeekDays] = useState(['SU']);

  const [originalRecurringEvent, setOriginalRecurringEvent] = useState();
  const [eventOccurrence, setEventOccurrence] = useState();
  const [recurringText, setRecurringText] = useState();
  const [recurringDelete, setRecurringDelete] = useState();
  const [isRecurringEditOpen, setRecurringEditOpen] = useState();
  const [newEvent, setNewEvent] = useState();
  const [recurringEditMode, setRecurringEditMode] = useState('current');
  const [editFromPopup, setEditFromPopup] = useState(false);

  // Set custom values to default
  const resetCustomValues = useCallback(() => {
    setRepeatType('daily');
    setRepeatNr('1');
    setCondition('never');
    setOccurrences('10');
    setMonth(1);
    setMonthlyDay('1');
    setYearlyDay('1');
    setWeekDays(['SU']);
    setSelectedRepeat('norepeat');
    setRepeatData(repeatData.filter((item) => item.value !== 'custom-value'));
  }, [repeatData]);

  const navigateTo = useCallback(() => {
    const rec = tempEvent.recurring;
    const d = new Date(tempEvent.start);
    let nextYear = 0;

    // Navigate the calendar to the correct view
    if (rec && rec.repeat === 'yearly') {
      if (d.getMonth() + 1 > +rec.month && d.getDay() > +rec.day) {
        nextYear = 1;
      }
      setSelectedDate(new Date(d.getFullYear() + nextYear, rec.month - 1, rec.day));
    } else {
      setSelectedDate(d);
    }
  }, [tempEvent]);

  const deleteRecurringEvent = useCallback(() => {
    switch (recurringEditMode) {
      case 'following': {
        let exceptions = tempEvent.recurringException || [];
        exceptions = [...exceptions, tempEvent.start];

        const newE = { ...originalRecurringEvent, recurringException: exceptions };
        newE.recurring.until = tempEvent.start;
        const i = myEvents.findIndex((x) => x.id === originalRecurringEvent.id);
        const newEvList = [...myEvents];

        newEvList.splice(i, 1, newE);
        setMyEvents(newEvList);
        break;
      }
      case 'all':
        setMyEvents(myEvents.filter((item) => item.id !== tempEvent.id));
        break;
      case 'current':
      default: {
        let currentExceptions = tempEvent.recurringException || [];
        currentExceptions = [...currentExceptions, tempEvent.start];

        const newEv = { ...originalRecurringEvent, recurringException: currentExceptions };
        const index = myEvents.findIndex((x) => x.id === originalRecurringEvent.id);
        const newEventList = [...myEvents];

        newEventList.splice(index, 1, newEv);
        setMyEvents(newEventList);
        break;
      }
    }
    setOpen(false);
    setRecurringEditOpen(false);
  }, [myEvents, originalRecurringEvent, recurringEditMode, tempEvent]);

  const getCustomRule = useCallback(() => {
    let recurringRule;
    const d = editFromPopup ? popupEventDate[0] : new Date(tempEvent.start);
    const weekday = d.getDay();
    const monthday = d.getDate();
    const weekNr = getWeekDayNum(d);

    if (editFromPopup && tempEvent.start && tempEvent.recurring) {
      switch (selectedRepeat) {
        default:
        case 'daily':
          recurringRule = {
            repeat: 'daily',
          };
          break;
        case 'weekly':
          recurringRule = {
            repeat: 'weekly',
            weekDays: days[weekday].short,
          };
          break;
        case 'monthly':
          recurringRule = {
            repeat: 'monthly',
            day: monthday,
          };
          break;
        case 'monthly-pos':
          recurringRule = {
            repeat: 'monthly',
            weekDays: days[weekday].short,
            pos: weekNr,
          };
          break;
        case 'yearly':
          recurringRule = {
            repeat: 'yearly',
            day: monthday,
            month: d.getMonth() + 1,
          };
          break;
        case 'yearly-pos':
          recurringRule = {
            repeat: 'yearly',
            month: d.getMonth() + 1,
            weekDays: days[weekday].short,
            pos: weekNr,
          };
          break;
      }
    } else {
      switch (selectedRepeat) {
        case 'daily':
          recurringRule = { repeat: 'daily' };
          break;
        case 'weekly':
          recurringRule = { repeat: 'weekly', weekDays: days[d.getDay()].short };
          break;
        case 'monthly':
          recurringRule = { repeat: 'monthly', day: d.getDate() };
          break;
        case 'monthly-pos':
          recurringRule = { repeat: 'monthly', weekDays: days[weekday].short, pos: weekNr };
          break;
        case 'yearly':
          recurringRule = { repeat: 'yearly', month: d.getMonth() + 1 };
          break;
        case 'yearly-pos':
          recurringRule = { repeat: 'yearly', month: d.getMonth() + 1, weekDays: days[weekday].short, pos: weekNr };
          break;
        case 'weekday':
          recurringRule = { repeat: 'weekly', weekDays: 'MO,TU,WE,TH,FR' };
          break;
        case 'custom':
        case 'custom-value':
          recurringRule = {
            repeat: repeatType,
            interval: repeatNr,
          };

          switch (repeatType) {
            case 'weekly':
              recurringRule.weekDays = weekDays.join(',');
              break;
            case 'monthly':
              recurringRule.day = monthlyDay;
              break;
            case 'yearly':
              recurringRule.day = yearlyDay;
              recurringRule.month = selectedMonth;
              break;

            default:
          }

          switch (condition) {
            case 'until':
              recurringRule.until = untilDate;
              break;
            case 'count':
              recurringRule.count = occurrences;
              break;
            default:
          }
          break;
        default:
      }
    }
    return recurringRule;
  }, [
    condition,
    editFromPopup,
    monthlyDay,
    occurrences,
    popupEventDate,
    repeatNr,
    repeatType,
    selectedMonth,
    selectedRepeat,
    tempEvent,
    untilDate,
    weekDays,
    yearlyDay,
  ]);

  const saveEvent = useCallback(() => {
    const newEv = {
      id: tempEvent.id,
      title: popupEventTitle,
      description: popupEventDescription,
      start: popupEventDate[0],
      end: popupEventDate[1],
      allDay: popupEventAllDay,
      color: tempEvent.color,
      recurring: getCustomRule(),
    };

    if (isEdit) {
      // Update the event in the list
      const index = myEvents.findIndex((x) => x.id === tempEvent.id);
      const newEventList = [...myEvents];

      newEventList.splice(index, 1, newEv);
      setMyEvents(newEventList);
      // Here you can update the event in your storage as well
      // ...
    } else {
      // Add the new event to the list
      setMyEvents([...myEvents, newEv]);
      // Here you can add the event to your storage as well
      // ...
    }

    // Navigate the calendar
    navigateTo();
    // Close the popup
    setOpen(false);
  }, [tempEvent, popupEventTitle, popupEventDescription, popupEventDate, popupEventAllDay, getCustomRule, isEdit, navigateTo, myEvents]);

  const deleteEvent = useCallback(
    (event) => {
      setMyEvents(myEvents.filter((item) => item.id !== event.id));
    },
    [myEvents],
  );

  const updateOptionDates = useCallback(
    (d) => {
      const weekday = d.getDay();
      const monthday = d.getDate();
      const newData = repeatData.slice(0);
      const weekNr = getWeekDayNum(d);

      for (let i = 0; i < newData.length; ++i) {
        const item = newData[i];
        switch (item.value) {
          case 'weekly':
            item.text = 'Weekly on ' + days[weekday].name;
            break;
          case 'monthly':
            item.text = 'Monthly on day ' + monthday;
            break;
          case 'monthly-pos':
            item.text = 'Monthly on the ' + ordinalList[weekNr] + ' ' + days[weekday].name;
            break;
          case 'yearly':
            item.text = 'Annually on ' + months[d.getMonth()].text + ' ' + monthday;
            break;
          case 'yearly-pos':
            item.text = 'Annually on the ' + ordinalList[weekNr] + ' ' + days[weekday].name + ' of ' + months[d.getMonth()];
            break;
          default:
        }
      }
      setRepeatData(newData);
    },
    [repeatData],
  );

  const loadPopupForm = useCallback(
    (event) => {
      const startDate = new Date(event.start);
      setTitle(event.title);
      setDescription(event.description);
      setDate([startDate, event.end]);
      setUntilDate(formatDate('YYYY-MM-DD', new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + 1)));
      setAllDay(event.allDay || false);

      const d = new Date(event.start);
      const weekday = d.getDay();
      const monthday = d.getDate();
      const newData = repeatData.slice(0);
      const weekNr = getWeekDayNum(d);

      // Update select texts by selected date
      for (let i = 0; i < newData.length; ++i) {
        const item = newData[i];
        switch (item.value) {
          case 'weekly':
            item.text = 'Weekly on ' + days[weekday].name;
            break;
          case 'monthly':
            item.text = 'Monthly on day ' + monthday;
            break;
          case 'monthly-pos':
            item.text = 'Monthly on the ' + ordinalList[weekNr] + ' ' + days[weekday].name;
            break;
          case 'yearly':
            item.text = 'Annually on ' + months[d.getMonth()].text + ' ' + monthday;
            break;
          case 'yearly-pos':
            item.text = 'Annually on the ' + ordinalList[weekNr] + ' ' + days[weekday].name + ' of ' + months[d.getMonth()].text;
            break;
          default:
        }
      }

      setRepeatData(newData);

      const rec = event.recurring;

      if (rec) {
        setRepeatType(rec.repeat);
        setWeekDays(rec.repeat === 'weekly' ? rec.weekDays.split(',') : ['SU']);
        if (rec.interval) {
          // Set custom text
          let customText = '';
          const nr = rec.interval;

          setRepeatNr(nr);

          switch (rec.repeat) {
            case 'daily':
              customText = nr > 1 ? 'Every ' + nr + ' days' : 'Daily';
              break;
            case 'weekly':
              customText = nr > 1 ? 'Every ' + nr + ' weeks' : 'Weekly';
              customText += ' on ' + rec.weekDays;
              break;
            case 'monthly':
              setMonthlyDay(rec.day);
              customText = nr > 1 ? 'Every ' + nr + ' months' : 'Monthly';
              customText += ' on day ' + rec.day;
              break;
            case 'yearly':
              setYearlyDay(rec.day);
              setMonth(rec.month);
              customText = nr > 1 ? 'Every ' + nr + ' years' : 'Annualy';
              customText += ' on ' + months[rec.month - 1].text + ' ' + rec.day;
              break;
            default:
          }

          if (rec.until) {
            setCondition('until');
            setUntilDate(rec.until);
            customText += ' until ' + formatDate('MMMM D, YYYY', new Date(rec.until));
          } else if (rec.count) {
            setCondition('count');
            setOccurrences(rec.count);
            customText += ', ' + rec.count + ' times';
          } else {
            setCondition('never');
          }

          // Add custom value
          setRepeatData([...repeatData, { value: 'custom-value', text: customText }]);
          // Set custom value
          setSelectedRepeat('custom-value');
        } else if (rec.weekDays === 'MO,TU,WE,TH,FR') {
          setSelectedRepeat('weekday');
        } else {
          setSelectedRepeat(rec.repeat + (rec.pos ? '-pos' : ''));
        }
      } else {
        resetCustomValues();
      }
    },
    [repeatData, resetCustomValues],
  );

  // Handle popup form changes

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

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

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

  const dateChange = useCallback(
    (args) => {
      const d = args.value;
      setDate(d);
      updateOptionDates(d[0]);
    },
    [updateOptionDates],
  );

  const onDeleteClick = useCallback(() => {
    if (tempEvent.recurring) {
      setRecurringText('Delete');
      setRecurringDelete(true);
      setRecurringEditOpen(true);
    } else {
      deleteEvent(tempEvent);
      setOpen(false);
    }
  }, [deleteEvent, tempEvent]);

  const populateMonthDays = useCallback((month, type) => {
    const newValues = getMonthDays(month);

    if (type === 'monthly') {
      setMonthlyDays(newValues);
      setMonthlyDay(1);
    } else {
      setYearlyDays(newValues);
      setYearlyDay(1);
    }
  }, []);

  const repeatChange = useCallback((ev) => {
    setSelectedRepeat(ev.value);
  }, []);

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

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

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

  const untilDateChange = useCallback((ev) => {
    setUntilDate(ev.value);
  }, []);

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

  const monthsChange = useCallback(
    (ev) => {
      setMonth(ev.value);
      populateMonthDays(ev.value, 'yearly');
    },
    [populateMonthDays],
  );

  const monthlyDayChange = useCallback((ev) => {
    setMonthlyDay(ev.value);
  }, []);

  const yearlyDayChange = useCallback((ev) => {
    setYearlyDay(ev.value);
  }, []);

  const weekDayChange = useCallback(
    (ev) => {
      const value = ev.target.value;
      if (ev.target.checked) {
        setWeekDays([...weekDays, ev.target.value]);
      } else {
        setWeekDays(weekDays.filter((item) => item !== value));
      }
    },
    [weekDays],
  );

  const onSelectedDateChange = useCallback((event) => {
    setSelectedDate(event.date);
  }, []);

  const onEventClick = useCallback(
    (args) => {
      const event = args.event;

      setEdit(true);
      setTempEvent({ ...args.event });

      // Recurring event
      if (event.recurring) {
        setOriginalRecurringEvent(event.original);
        setEventOccurrence({ ...event });
        loadPopupForm(event);
      } else {
        setOriginalRecurringEvent({});
        loadPopupForm(event);
      }

      setAnchor(args.domEvent.target);
      setOpen(true);
    },
    [loadPopupForm],
  );

  const onEventUpdate = useCallback((args) => {
    const event = args.event;
    if (event.recurring) {
      setOriginalRecurringEvent(args.oldEvent);
      setTempEvent(event);
      setEventOccurrence(args.oldEventOccurrence);
      if (args.domEvent.keyCode === 46) {
        setRecurringText('Delete');
        setRecurringDelete(true);
        setRecurringEditOpen(true);
      } else {
        setRecurringText('Edit');
        setRecurringDelete(false);
        setRecurringEditOpen(true);
      }
      return false;
    }
  }, []);

  const onEventCreate = useCallback((args) => {
    const originEvent = args.originEvent;
    if (originEvent && originEvent.recurring) {
      setNewEvent(args.event);
      return false;
    }
  }, []);

  const onEventCreated = useCallback(
    (args) => {
      setEdit(false);
      resetCustomValues();
      setTempEvent(args.event);
      // Fill popup form with event data
      loadPopupForm(args.event);
      setAnchor(args.target);
      // Open the popup
      setOpen(true);
    },
    [loadPopupForm, resetCustomValues],
  );

  const onEventDeleted = useCallback(
    (args) => {
      deleteEvent(args.event);
    },
    [deleteEvent],
  );

  const onEventUpdated = useCallback(() => {
    // Here you can update the event in your storage as well, after drag & drop or resize
    // ...
  }, []);

  // Datepicker options
  const controls = useMemo(() => (popupEventAllDay ? ['calendar'] : ['calendar', 'time']), [popupEventAllDay]);
  const respSetting = useMemo(
    () =>
      popupEventAllDay
        ? {
            xsmall: {
              controls: ['date'],
            },
            medium: {
              controls: ['calendar'],
              touchUi: false,
            },
          }
        : {
            xsmall: {
              controls: ['datetime'],
            },
            medium: {
              controls: ['calendar', 'time'],
              touchUi: false,
            },
          },
    [popupEventAllDay],
  );

  // Popup options
  const headerText = useMemo(() => (isEdit ? 'Edit event' : 'New Event'), [isEdit]);
  const popupButtons = useMemo(() => {
    if (isEdit) {
      return [
        'cancel',
        {
          handler: () => {
            if (Object.keys(originalRecurringEvent).length !== 0) {
              setEditFromPopup(true);
              setRecurringText('Edit');
              setRecurringDelete(false);
              setRecurringEditOpen(true);
            } else {
              saveEvent();
            }
          },
          keyCode: 'enter',
          text: 'Save',
          cssClass: 'mbsc-popup-button-primary',
        },
      ];
    } else {
      return [
        'cancel',
        {
          handler: () => {
            saveEvent();
          },
          keyCode: 'enter',
          text: 'Add',
          cssClass: 'mbsc-popup-button-primary',
        },
      ];
    }
  }, [isEdit, originalRecurringEvent, saveEvent]);

  const onPopupClose = useCallback(() => {
    setRepeatData(repeatData.filter((item) => item.value !== 'custom-value'));
    if (!isEdit) {
      // Refresh the list, if add popup was canceled, to remove the temporary event
      setMyEvents([...myEvents]);
    }
    setEditFromPopup(false);
    setOpen(false);
  }, [isEdit, myEvents, repeatData]);

  const recurringEditButtons = useMemo(
    () => [
      'cancel',
      {
        handler: () => {
          if (recurringDelete) {
            deleteRecurringEvent();
          } else {
            const updatedEvent = { ...tempEvent };
            if (editFromPopup) {
              updatedEvent.title = popupEventTitle;
              updatedEvent.description = popupEventDescription;
              updatedEvent.start = popupEventDate[0];
              updatedEvent.end = popupEventDate[1];
              updatedEvent.allDay = popupEventAllDay;
              updatedEvent.recurring = getCustomRule();
            }

            if (recurringEditMode === 'current') {
              delete updatedEvent.id;
              delete updatedEvent.recurring;
              delete updatedEvent.recurringException;
            }

            const events = updateRecurringEvent(
              originalRecurringEvent,
              eventOccurrence,
              editFromPopup ? null : newEvent,
              editFromPopup ? updatedEvent : null,
              recurringEditMode,
            );

            // Update event
            let newEventList = [...myEvents];
            const index = newEventList.findIndex((x) => x.id === events.updatedEvent.id);
            newEventList[index] = events.updatedEvent;

            // Add new event
            if (events.newEvent) {
              newEventList = [...newEventList, events.newEvent];
            }

            setMyEvents(newEventList);

            setOpen(false);
            setRecurringEditOpen(false);
          }
        },
        keyCode: 'enter',
        text: 'Ok',
        cssClass: 'mbsc-popup-button-primary',
      },
    ],
    [
      deleteRecurringEvent,
      editFromPopup,
      eventOccurrence,
      getCustomRule,
      myEvents,
      newEvent,
      originalRecurringEvent,
      popupEventAllDay,
      popupEventDate,
      popupEventDescription,
      popupEventTitle,
      recurringDelete,
      recurringEditMode,
      tempEvent,
    ],
  );

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

  const onRecurringEditClose = useCallback(() => {
    setRecurringEditMode('current');
    setRecurringEditOpen(false);
  }, []);

  return (
    <>
      <Eventcalendar
        view={myView}
        data={myEvents}
        clickToCreate="double"
        dragToCreate={true}
        dragToMove={true}
        dragToResize={true}
        selectedDate={mySelectedDate}
        onSelectedDateChange={onSelectedDateChange}
        onEventClick={onEventClick}
        onEventUpdate={onEventUpdate}
        onEventCreate={onEventCreate}
        onEventCreated={onEventCreated}
        onEventDeleted={onEventDeleted}
        onEventUpdated={onEventUpdated}
      />
      <Popup
        display="bottom"
        contentPadding={false}
        fullScreen={true}
        scrollLock={false}
        height={500}
        headerText={headerText}
        anchor={anchor}
        buttons={popupButtons}
        isOpen={isOpen}
        onClose={onPopupClose}
        responsive={responsivePopup}
        cssClass="md-recurring-event-editor-popup"
      >
        <div className="mbsc-form-group">
          <Input label="Title" value={popupEventTitle} onChange={titleChange} />
          <Textarea label="Description" value={popupEventDescription} onChange={descriptionChange} />
        </div>
        <div className="mbsc-form-group">
          <Switch label="All-day" checked={popupEventAllDay} onChange={allDayChange} />
          <Input ref={startRef} label="Starts" />
          <Input ref={endRef} label="Ends" />
          <Datepicker
            select="range"
            controls={controls}
            touchUi={true}
            startInput={start}
            endInput={end}
            showRangeLabels={false}
            responsive={respSetting}
            onChange={dateChange}
            value={popupEventDate}
          />
          <Select data={repeatData} label="Repeats" value={selectedRepeat} responsive={responsiveSelect} onChange={repeatChange} />
        </div>
        <div className="mbsc-form-group">
          {(selectedRepeat === 'custom' || selectedRepeat === 'custom-value') && (
            <div>
              <div>
                <SegmentedGroup onChange={repeatTypeChange}>
                  <Segmented value="daily" checked={repeatType === 'daily'}>
                    Daily
                  </Segmented>
                  <Segmented value="weekly" checked={repeatType === 'weekly'}>
                    Weekly
                  </Segmented>
                  <Segmented value="monthly" checked={repeatType === 'monthly'}>
                    Monthly
                  </Segmented>
                  <Segmented value="yearly" checked={repeatType === 'yearly'}>
                    Yearly
                  </Segmented>
                </SegmentedGroup>

                <div className="md-recurrence-options">
                  <span>Repeat every</span>
                  <span className="md-recurrence-input md-recurrence-input-nr">
                    <Input min={1} value={repeatNr} onChange={repeatNrChange} inputStyle="outline" />
                  </span>
                  {repeatType === 'daily' && <span>days</span>}
                  {repeatType === 'weekly' && <span>weeks</span>}
                  {repeatType === 'monthly' && (
                    <span>
                      month(s) on day
                      <span className="md-recurrence-input md-recurrence-input-nr">
                        <Select data={monthlyDays} value={monthlyDay} onChange={monthlyDayChange} inputStyle="outline" />
                      </span>
                    </span>
                  )}
                  {repeatType === 'yearly' && (
                    <span>
                      year(s) <br />
                      on day
                      <span className="md-recurrence-input md-recurrence-input-nr">
                        <Select data={yearlyDays} value={yearlyDay} onChange={yearlyDayChange} inputStyle="outline" />
                      </span>
                      <span>of</span>
                      <span className="md-recurrence-input">
                        <Select data={months} value={selectedMonth} onChange={monthsChange} inputStyle="outline" />
                      </span>
                    </span>
                  )}

                  {repeatType === 'daily' && (
                    <p className="md-recurrence-desc">The event will be repeated every day or every x days, depending on the value</p>
                  )}
                  {repeatType === 'weekly' && (
                    <p className="md-recurrence-desc">The event will be repeated every x weeks on specific weekdays</p>
                  )}
                  {repeatType === 'monthly' && (
                    <p className="md-recurrence-desc">The event will be repeated every x month on specific day of the month</p>
                  )}
                  {repeatType === 'yearly' && (
                    <p className="md-recurrence-desc">The event will be repeated every x years on specific day of a specific month</p>
                  )}
                </div>

                {repeatType === 'weekly' && (
                  <SegmentedGroup select="multiple" onChange={weekDayChange}>
                    <Segmented value="SU" checked={weekDays.indexOf('SU') >= 0}>
                      Sun
                    </Segmented>
                    <Segmented value="MO" checked={weekDays.indexOf('MO') >= 0}>
                      Mon
                    </Segmented>
                    <Segmented value="TU" checked={weekDays.indexOf('TU') >= 0}>
                      Tue
                    </Segmented>
                    <Segmented value="WE" checked={weekDays.indexOf('WE') >= 0}>
                      Wed
                    </Segmented>
                    <Segmented value="TH" checked={weekDays.indexOf('TH') >= 0}>
                      Thu
                    </Segmented>
                    <Segmented value="FR" checked={weekDays.indexOf('FR') >= 0}>
                      Fri
                    </Segmented>
                    <Segmented value="SA" checked={weekDays.indexOf('SA') >= 0}>
                      Sat
                    </Segmented>
                  </SegmentedGroup>
                )}

                <div className="md-recurrence-ends">Ends</div>

                <div className="mbsc-form-group">
                  <RadioGroup>
                    <Radio
                      label="Never"
                      position="start"
                      description="The event will repeat indefinitely"
                      checked={condition === 'never'}
                      onChange={conditionChange}
                      value="never"
                    />
                    <Radio checked={condition === 'until'} onChange={conditionChange} value="until" position="start">
                      Until
                      <span className="md-recurrence-input">
                        <Datepicker
                          inputStyle="outline"
                          controls={['calendar']}
                          display="anchored"
                          touchUi={false}
                          dateFormat="YYYY-MM-DD"
                          returnFormat="iso8601"
                          value={untilDate}
                          onChange={untilDateChange}
                          onOpen={() => setCondition('until')}
                        />
                      </span>
                      <span className="mbsc-description">The event will run until it reaches a specific date</span>
                    </Radio>
                    <Radio checked={condition === 'count'} onChange={conditionChange} value="count" position="start">
                      After
                      <span className="md-recurrence-input md-recurrence-input-nr">
                        <Input
                          inputStyle="outline"
                          value={occurrences}
                          onChange={occurrancesChange}
                          onClick={() => setCondition('count')}
                        />
                        occurrences
                        <span className="mbsc-description">The event will repeat until it reaches a certain amount of occurrences</span>
                      </span>
                    </Radio>
                  </RadioGroup>
                </div>
              </div>
            </div>
          )}
          {isEdit && (
            <div className="mbsc-button-group">
              <Button className="mbsc-button-block" color="danger" variant="outline" onClick={onDeleteClick}>
                Delete event
              </Button>
            </div>
          )}
        </div>
      </Popup>
      <Popup
        display="bottom"
        contentPadding={false}
        buttons={recurringEditButtons}
        isOpen={isRecurringEditOpen}
        onClose={onRecurringEditClose}
      >
        <div className="mbsc-form-group">
          <div className="mbsc-form-group-title">{recurringText} recurring event</div>
          <RadioGroup onChange={recurringEditModeChange}>
            <Radio label="This event only" checked={recurringEditMode === 'current'} onChange={recurringEditModeChange} value="current" />
            <Radio
              label="This and following events"
              checked={recurringEditMode === 'following'}
              onChange={recurringEditModeChange}
              value="following"
            />
            <Radio label="All events" checked={recurringEditMode === 'all'} onChange={recurringEditModeChange} value="all" />
          </RadioGroup>
        </div>
      </Popup>
    </>
  );
}

export default App;
```

### TSX

```tsx
import {
  Button,
  Datepicker,
  Eventcalendar,
  formatDate,
  Input,
  MbscCalendarEvent,
  MbscDatepickerChangeEvent,
  MbscDatepickerControl,
  MbscDatepickerOptions,
  MbscDateType,
  MbscEventcalendarView,
  MbscEventClickEvent,
  MbscEventCreatedEvent,
  MbscEventCreateEvent,
  MbscEventDeletedEvent,
  MbscEventUpdateEvent,
  MbscPopupButton,
  MbscPopupOptions,
  MbscRecurrenceRule,
  MbscResponsiveOptions,
  MbscSelectChangeEvent,
  MbscSelectedDateChangeEvent,
  MbscSelectOptions,
  Popup,
  Radio,
  RadioGroup,
  Segmented,
  SegmentedGroup,
  Select,
  setOptions,
  Switch,
  Textarea,
  updateRecurringEvent,
} from '@mobiscroll/react';
import { ChangeEvent, FC, useCallback, useMemo, useState } from 'react';

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

const defaultEvents: MbscCalendarEvent[] = [
  {
    id: 1,
    start: '2026-10-21T00:00',
    end: '2026-10-24T00:00',
    title: 'Alice OFF',
    allDay: true,
    color: '#67ab0d',
  },
  {
    id: 2,
    start: '18:00',
    end: '20:00',
    recurring: {
      repeat: 'weekly',
      weekDays: 'MO',
    },
    title: 'Football training',
    allDay: false,
    color: '#fd966a',
  },
  {
    id: 3,
    start: '17:00',
    end: '17:30',
    recurring: {
      repeat: 'monthly',
      day: 1,
    },
    title: 'Rent payment',
    allDay: false,
    color: '#3ea39e',
  },
  {
    id: 4,
    recurring: {
      repeat: 'yearly',
      day: 6,
      month: 5,
    },
    title: 'Dexter BD',
    allDay: true,
    color: '#d00f0f',
  },
];

const myView: MbscEventcalendarView = {
  calendar: { labels: true },
};

const responsivePopup: MbscResponsiveOptions<MbscPopupOptions> = {
  medium: {
    display: 'anchored',
    width: 510,
    fullScreen: false,
    touchUi: false,
  },
};

const responsiveSelect: MbscResponsiveOptions<MbscSelectOptions> = {
  xsmall: {
    touchUi: true,
  },
  small: {
    touchUi: false,
  },
};

const days = [
  { name: 'Sunday', short: 'SU', checked: true },
  { name: 'Monday', short: 'MO', checked: false },
  { name: 'Tuesday', short: 'TU', checked: false },
  { name: 'Wednesday', short: 'WE', checked: false },
  { name: 'Thursday', short: 'TH', checked: false },
  { name: 'Friday', short: 'FR', checked: false },
  { name: 'Saturday', short: 'SA', checked: false },
];

const months = [
  { value: 1, text: 'January' },
  { value: 2, text: 'February' },
  { value: 3, text: 'March' },
  { value: 4, text: 'April' },
  { value: 5, text: 'May' },
  { value: 6, text: 'June' },
  { value: 7, text: 'July' },
  { value: 8, text: 'August' },
  { value: 9, text: 'September' },
  { value: 10, text: 'October' },
  { value: 11, text: 'November' },
  { value: 12, text: 'December' },
];

const ordinalList: { [key: number]: string } = { 1: 'first', 2: 'second', 3: 'third', 4: 'fourth' };

// Returns the weeknumber of the passed date
function getWeekDayNum(date: Date): number {
  const year = date.getFullYear();
  const month = date.getMonth();
  const firstDayOfMonth = new Date(year, month, 1);
  const lastDayOfMonth = new Date(year, month + 1, 0);
  let count = 0;

  for (let d = firstDayOfMonth; d < lastDayOfMonth; d.setDate(d.getDate() + 7)) {
    if (date >= d) {
      count++;
    } else {
      break;
    }
  }

  return Math.max(1, count);
}

function getMonthDays(month: number) {
  const day30 = [2, 4, 6, 9, 11];
  const newValues = [];
  for (let i = 1; i <= 31; i++) {
    if (!(i === 31 && day30.includes(month)) && !(i === 30 && month === 2)) {
      newValues.push(i);
    }
  }
  return newValues;
}

const App: FC = () => {
  const [myEvents, setMyEvents] = useState<MbscCalendarEvent[]>(defaultEvents);
  const [tempEvent, setTempEvent] = useState<MbscCalendarEvent>();
  const [isOpen, setOpen] = useState<boolean>(false);
  const [isEdit, setEdit] = useState<boolean>(false);
  const [anchor, setAnchor] = useState<HTMLElement>();
  const [start, startRef] = useState<Input | null>(null);
  const [end, endRef] = useState<Input | null>(null);
  const [popupEventTitle, setTitle] = useState<string | undefined>('');
  const [popupEventDescription, setDescription] = useState<string>('');
  const [popupEventAllDay, setAllDay] = useState<boolean>(true);
  const [popupEventDate, setDate] = useState<MbscDateType[]>([]);
  const [mySelectedDate, setSelectedDate] = useState<MbscDateType>();

  // Recurring editor data
  const [repeatData, setRepeatData] = useState([
    { value: 'norepeat', text: 'Does not repeat' },
    { value: 'daily', text: 'Daily' },
    { value: 'weekly', text: 'Weekly' },
    { value: 'monthly', text: 'Monthly' },
    { value: 'monthly-pos', text: 'Monthly' },
    { value: 'yearly', text: 'Yearly' },
    { value: 'yearly-pos', text: 'Yearly' },
    { value: 'weekday', text: 'Every weekday (Monday to Friday)' },
    { value: 'custom', text: 'Custom' },
  ]);
  const [selectedRepeat, setSelectedRepeat] = useState<string>('norepeat');
  const [repeatType, setRepeatType] = useState<'daily' | 'weekly' | 'monthly' | 'yearly' | undefined>('daily');
  const [repeatNr, setRepeatNr] = useState<number>(1);
  const [condition, setCondition] = useState<string>('never');
  const [untilDate, setUntilDate] = useState<string>();
  const [occurrences, setOccurrences] = useState<number>(10);
  const [selectedMonth, setMonth] = useState<number>(1);
  const [monthlyDays, setMonthlyDays] = useState<number[]>(() => getMonthDays(1));
  const [monthlyDay, setMonthlyDay] = useState<number>(1);
  const [yearlyDays, setYearlyDays] = useState<number[]>(() => getMonthDays(1));
  const [yearlyDay, setYearlyDay] = useState<number>(1);
  const [weekDays, setWeekDays] = useState<string[]>(['SU']);

  const [originalRecurringEvent, setOriginalRecurringEvent] = useState<MbscCalendarEvent>();
  const [eventOccurrence, setEventOccurrence] = useState<MbscCalendarEvent>();
  const [recurringText, setRecurringText] = useState<string>();
  const [recurringDelete, setRecurringDelete] = useState<boolean>();
  const [isRecurringEditOpen, setRecurringEditOpen] = useState<boolean>();
  const [newEvent, setNewEvent] = useState<MbscCalendarEvent>();
  const [recurringEditMode, setRecurringEditMode] = useState<'all' | 'current' | 'following'>('current');
  const [editFromPopup, setEditFromPopup] = useState<boolean>(false);

  // Set custom values to default
  const resetCustomValues = useCallback(() => {
    setRepeatType('daily');
    setRepeatNr(1);
    setCondition('never');
    setOccurrences(10);
    setMonth(1);
    setMonthlyDay(1);
    setYearlyDay(1);
    setWeekDays(['SU']);
    setSelectedRepeat('norepeat');
    setRepeatData(repeatData.filter((item) => item.value !== 'custom-value'));
  }, [repeatData]);

  const navigateTo = useCallback(() => {
    const rec = tempEvent!.recurring;
    const d = new Date(tempEvent!.start as string);
    let nextYear = 0;

    // Navigate the calendar to the correct view
    if (rec && rec.repeat === 'yearly') {
      if (d.getMonth() + 1 > +rec.month! && d.getDay() > +rec.day!) {
        nextYear = 1;
      }
      setSelectedDate(new Date(d.getFullYear() + nextYear, +rec.month! - 1, +rec.day!));
    } else {
      setSelectedDate(d);
    }
  }, [tempEvent]);

  const deleteRecurringEvent = useCallback(() => {
    switch (recurringEditMode) {
      case 'following': {
        let exceptions = tempEvent!.recurringException || [];
        exceptions = [...(exceptions as Array<string | object | Date>), tempEvent!.start];

        const newE = { ...originalRecurringEvent, recurringException: exceptions };
        (newE.recurring as MbscRecurrenceRule).until = tempEvent!.start;
        const i = myEvents.findIndex((x) => x.id === originalRecurringEvent!.id);
        const newEvList = [...myEvents];

        newEvList.splice(i, 1, newE);
        setMyEvents(newEvList);
        break;
      }
      case 'all':
        setMyEvents(myEvents.filter((item) => item.id !== tempEvent!.id));
        break;
      case 'current':
      default: {
        let currentExceptions = (tempEvent!.recurringException as Array<string | object | Date>) || [];
        currentExceptions = [...currentExceptions, tempEvent!.start!];

        const newEv = { ...originalRecurringEvent, recurringException: currentExceptions };
        const index = myEvents.findIndex((x) => x.id === originalRecurringEvent!.id);
        const newEventList = [...myEvents];

        newEventList.splice(index, 1, newEv);
        setMyEvents(newEventList);
        break;
      }
    }
    setOpen(false);
    setRecurringEditOpen(false);
  }, [myEvents, originalRecurringEvent, recurringEditMode, tempEvent]);

  const getCustomRule = useCallback(() => {
    let recurringRule: MbscRecurrenceRule = {};
    const d = editFromPopup ? (popupEventDate[0] as Date) : new Date(tempEvent!.start as string);
    const weekday = d.getDay();
    const monthday = d.getDate();
    const weekNr = getWeekDayNum(d);

    if (editFromPopup && tempEvent!.start && tempEvent!.recurring) {
      switch (selectedRepeat) {
        default:
        case 'daily':
          recurringRule = {
            repeat: 'daily',
          };
          break;
        case 'weekly':
          recurringRule = {
            repeat: 'weekly',
            weekDays: days[weekday].short,
          };
          break;
        case 'monthly':
          recurringRule = {
            repeat: 'monthly',
            day: monthday,
          };
          break;
        case 'monthly-pos':
          recurringRule = {
            repeat: 'monthly',
            weekDays: days[weekday].short,
            pos: weekNr,
          };
          break;
        case 'yearly':
          recurringRule = {
            repeat: 'yearly',
            day: monthday,
            month: d.getMonth() + 1,
          };
          break;
        case 'yearly-pos':
          setTempEvent((e) => ({
            ...e,
            recurring: {
              repeat: 'yearly',
              month: d.getMonth() + 1,
              weekDays: days[weekday].short,
              pos: weekNr,
            },
          }));
          break;
      }
    } else {
      switch (selectedRepeat) {
        case 'daily':
          recurringRule = { repeat: 'daily' };
          break;
        case 'weekly':
          recurringRule = { repeat: 'weekly', weekDays: days[d.getDay()].short };
          break;
        case 'monthly':
          recurringRule = { repeat: 'monthly', day: d.getDate() };
          break;
        case 'monthly-pos':
          recurringRule = { repeat: 'monthly', weekDays: days[weekday].short, pos: weekNr };
          break;
        case 'yearly':
          recurringRule = { repeat: 'yearly', month: d.getMonth() + 1 };
          break;
        case 'yearly-pos':
          recurringRule = { repeat: 'yearly', month: d.getMonth() + 1, weekDays: days[weekday].short, pos: weekNr };
          break;
        case 'weekday':
          recurringRule = { repeat: 'weekly', weekDays: 'MO,TU,WE,TH,FR' };
          break;
        case 'custom':
        case 'custom-value':
          recurringRule = {
            repeat: repeatType,
            interval: repeatNr,
          };

          switch (repeatType) {
            case 'weekly':
              recurringRule.weekDays = weekDays.join(',');
              break;
            case 'monthly':
              recurringRule.day = monthlyDay;
              break;
            case 'yearly':
              recurringRule.day = yearlyDay;
              recurringRule.month = selectedMonth;
              break;

            default:
          }

          switch (condition) {
            case 'until':
              recurringRule.until = untilDate;
              break;
            case 'count':
              recurringRule.count = occurrences;
              break;
            default:
          }
          break;
        default:
      }
    }
    return recurringRule;
  }, [
    condition,
    editFromPopup,
    monthlyDay,
    occurrences,
    popupEventDate,
    repeatNr,
    repeatType,
    selectedMonth,
    selectedRepeat,
    tempEvent,
    untilDate,
    weekDays,
    yearlyDay,
  ]);

  const saveEvent = useCallback(() => {
    const newEv = {
      id: tempEvent!.id,
      title: popupEventTitle,
      description: popupEventDescription,
      start: popupEventDate[0],
      end: popupEventDate[1],
      allDay: popupEventAllDay,
      color: tempEvent!.color,
      recurring: getCustomRule(),
    };

    if (isEdit) {
      // Update the event in the list
      const index = myEvents.findIndex((x) => x.id === tempEvent!.id);
      const newEventList = [...myEvents];

      newEventList.splice(index, 1, newEv);
      setMyEvents(newEventList);
      // Here you can update the event in your storage as well
      // ...
    } else {
      // Add the new event to the list
      setMyEvents([...myEvents, newEv]);
      // Here you can add the event to your storage as well
      // ...
    }

    // Navigate the calendar
    navigateTo();
    // Close the popup
    setOpen(false);
  }, [tempEvent, popupEventTitle, popupEventDescription, popupEventDate, popupEventAllDay, getCustomRule, isEdit, navigateTo, myEvents]);

  const deleteEvent = useCallback(
    (event: MbscCalendarEvent) => {
      setMyEvents(myEvents.filter((item) => item.id !== event.id));
    },
    [myEvents],
  );

  const updateOptionDates = useCallback(
    (d: Date) => {
      const weekday = d.getDay();
      const monthday = d.getDate();
      const newData = repeatData.slice(0);
      const weekNr = getWeekDayNum(d);

      for (let i = 0; i < newData.length; ++i) {
        const item = newData[i];
        switch (item.value) {
          case 'weekly':
            item.text = 'Weekly on ' + days[weekday].name;
            break;
          case 'monthly':
            item.text = 'Monthly on day ' + monthday;
            break;
          case 'monthly-pos':
            item.text = 'Monthly on the ' + ordinalList[weekNr] + ' ' + days[weekday].name;
            break;
          case 'yearly':
            item.text = 'Annually on ' + months[d.getMonth()].text + ' ' + monthday;
            break;
          case 'yearly-pos':
            item.text = 'Annually on the ' + ordinalList[weekNr] + ' ' + days[weekday].name + ' of ' + months[d.getMonth()];
            break;
          default:
        }
      }
      setRepeatData(newData);
    },
    [repeatData],
  );

  const loadPopupForm = useCallback(
    (event: MbscCalendarEvent) => {
      const startDate = new Date(event.start as string);
      setTitle(event.title);
      setDescription(event.description);
      setDate([startDate, new Date(event.end as string)]);
      setUntilDate(formatDate('YYYY-MM-DD', new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + 1)));
      setAllDay(event.allDay || false);

      const d = new Date(event.start as string);
      const weekday = d.getDay();
      const monthday = d.getDate();
      const newData = repeatData.slice(0);
      const weekNr = getWeekDayNum(d);

      // Update select texts by selected date
      for (let i = 0; i < newData.length; ++i) {
        const item = newData[i];
        switch (item.value) {
          case 'weekly':
            item.text = 'Weekly on ' + days[weekday].name;
            break;
          case 'monthly':
            item.text = 'Monthly on day ' + monthday;
            break;
          case 'monthly-pos':
            item.text = 'Monthly on the ' + ordinalList[weekNr] + ' ' + days[weekday].name;
            break;
          case 'yearly':
            item.text = 'Annually on ' + months[d.getMonth()].text + ' ' + monthday;
            break;
          case 'yearly-pos':
            item.text = 'Annually on the ' + ordinalList[weekNr] + ' ' + days[weekday].name + ' of ' + months[d.getMonth()].text;
            break;
          default:
        }
      }

      setRepeatData(newData);

      const rec = event.recurring as MbscRecurrenceRule;

      if (rec) {
        setRepeatType(rec.repeat);
        setWeekDays(rec.repeat === 'weekly' ? rec.weekDays!.split(',') : ['SU']);
        if (rec.interval) {
          // Set custom text
          let customText = '';
          const nr = rec.interval;

          setRepeatNr(nr);

          switch (rec.repeat) {
            case 'daily':
              customText = nr > 1 ? 'Every ' + nr + ' days' : 'Daily';
              break;
            case 'weekly':
              customText = nr > 1 ? 'Every ' + nr + ' weeks' : 'Weekly';
              customText += ' on ' + rec.weekDays;
              break;
            case 'monthly':
              setMonthlyDay(rec.day as number);
              customText = nr > 1 ? 'Every ' + nr + ' months' : 'Monthly';
              customText += ' on day ' + rec.day;
              break;
            case 'yearly':
              setYearlyDay(rec.day as number);
              setMonth(rec.month as number);
              customText = nr > 1 ? 'Every ' + nr + ' years' : 'Annualy';
              customText += ' on ' + months[+rec.month! - 1].text + ' ' + rec.day;
              break;
            default:
          }

          if (rec.until) {
            setCondition('until');
            setUntilDate(rec.until as string);
            customText += ' until ' + formatDate('MMMM D, YYYY', new Date(rec.until as string));
          } else if (rec.count) {
            setCondition('count');
            setOccurrences(rec.count);
            customText += ', ' + rec.count + ' times';
          } else {
            setCondition('never');
          }

          // Add custom value
          setRepeatData([...repeatData, { value: 'custom-value', text: customText }]);
          // Set custom value
          setSelectedRepeat('custom-value');
        } else if (rec.weekDays === 'MO,TU,WE,TH,FR') {
          setSelectedRepeat('weekday');
        } else {
          setSelectedRepeat(rec.repeat + (rec.pos ? '-pos' : ''));
        }
      } else {
        resetCustomValues();
      }
    },
    [repeatData, resetCustomValues],
  );

  // Handle popup form changes

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

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

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

  const dateChange = useCallback(
    (args: MbscDatepickerChangeEvent) => {
      const d = args.value as Date[];
      setDate(d);
      updateOptionDates(d[0]);
    },
    [updateOptionDates],
  );

  const onDeleteClick = useCallback(() => {
    if (tempEvent!.recurring) {
      setRecurringText('Delete');
      setRecurringDelete(true);
      setRecurringEditOpen(true);
    } else {
      deleteEvent(tempEvent!);
      setOpen(false);
    }
  }, [deleteEvent, tempEvent]);

  const populateMonthDays = useCallback((month: number, type: string) => {
    const newValues = getMonthDays(month);

    if (type === 'monthly') {
      setMonthlyDays(newValues);
      setMonthlyDay(1);
    } else {
      setYearlyDays(newValues);
      setYearlyDay(1);
    }
  }, []);

  const repeatChange = useCallback((args: MbscSelectChangeEvent) => {
    setSelectedRepeat(args.value);
  }, []);

  const repeatTypeChange = useCallback((ev: ChangeEvent<HTMLInputElement>) => {
    setRepeatType(ev.target.value as 'daily' | 'weekly' | 'monthly' | 'yearly');
  }, []);

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

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

  const untilDateChange = useCallback((args: MbscDatepickerChangeEvent) => {
    setUntilDate(args.value as string);
  }, []);

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

  const monthsChange = useCallback(
    (args: MbscSelectChangeEvent) => {
      setMonth(args.value);
      populateMonthDays(args.value, 'yearly');
    },
    [populateMonthDays],
  );

  const monthlyDayChange = useCallback((args: MbscSelectChangeEvent) => {
    setMonthlyDay(args.value);
  }, []);

  const yearlyDayChange = useCallback((args: MbscSelectChangeEvent) => {
    setYearlyDay(args.value);
  }, []);

  const weekDayChange = useCallback(
    (ev: ChangeEvent<HTMLInputElement>) => {
      const value = ev.target.value;
      if (ev.target.checked) {
        setWeekDays([...weekDays, ev.target.value]);
      } else {
        setWeekDays(weekDays.filter((item) => item !== value));
      }
    },
    [weekDays],
  );

  const onSelectedDateChange = useCallback((args: MbscSelectedDateChangeEvent) => {
    setSelectedDate(args.date);
  }, []);

  const onEventClick = useCallback(
    (args: MbscEventClickEvent) => {
      const event = args.event;

      setEdit(true);
      setTempEvent({ ...event });

      // Recurring event
      if (event.recurring) {
        setOriginalRecurringEvent(event.original);
        setEventOccurrence({ ...event });
        loadPopupForm(event);
      } else {
        setOriginalRecurringEvent({});
        loadPopupForm(event);
      }

      setAnchor(args.domEvent.target);
      setOpen(true);
    },
    [loadPopupForm],
  );

  const onEventUpdate = useCallback((args: MbscEventUpdateEvent) => {
    const event = args.event;
    if (event.recurring) {
      setOriginalRecurringEvent(args.oldEvent);
      setTempEvent(event);
      setEventOccurrence(args.oldEventOccurrence);
      if (args.domEvent.keyCode === 46) {
        setRecurringText('Delete');
        setRecurringDelete(true);
        setRecurringEditOpen(true);
      } else {
        setRecurringText('Edit');
        setRecurringDelete(false);
        setRecurringEditOpen(true);
      }
      return false;
    }
  }, []);

  const onEventCreate = useCallback((args: MbscEventCreateEvent) => {
    const originEvent = args.originEvent;
    if (originEvent && originEvent.recurring) {
      setNewEvent(args.event);
      return false;
    }
  }, []);

  const onEventCreated = useCallback(
    (args: MbscEventCreatedEvent) => {
      setEdit(false);
      resetCustomValues();
      setTempEvent(args.event);
      // Fill popup form with event data
      loadPopupForm(args.event);
      setAnchor(args.target!);
      // Open the popup
      setOpen(true);
    },
    [loadPopupForm, resetCustomValues],
  );

  const onEventDeleted = useCallback(
    (args: MbscEventDeletedEvent) => {
      deleteEvent(args.event);
    },
    [deleteEvent],
  );

  const onEventUpdated = useCallback(() => {
    // Here you can update the event in your storage as well, after drag & drop or resize
    // ...
  }, []);

  // Datepicker options
  const controls = useMemo<MbscDatepickerControl[]>(() => (popupEventAllDay ? ['calendar'] : ['calendar', 'time']), [popupEventAllDay]);
  const respSetting: MbscResponsiveOptions<MbscDatepickerOptions> = useMemo(
    () =>
      popupEventAllDay
        ? {
            xsmall: {
              controls: ['date'],
            },
            medium: {
              controls: ['calendar'],
              touchUi: false,
            },
          }
        : {
            xsmall: {
              controls: ['datetime'],
            },
            medium: {
              controls: ['calendar', 'time'],
              touchUi: false,
            },
          },
    [popupEventAllDay],
  );

  // Popup options
  const headerText = useMemo(() => (isEdit ? 'Edit event' : 'New Event'), [isEdit]);
  const popupButtons = useMemo<Array<string | MbscPopupButton>>(() => {
    if (isEdit) {
      return [
        'cancel',
        {
          handler: () => {
            if (Object.keys(originalRecurringEvent!).length !== 0) {
              setEditFromPopup(true);
              setRecurringText('Edit');
              setRecurringDelete(false);
              setRecurringEditOpen(true);
            } else {
              saveEvent();
            }
          },
          keyCode: 'enter',
          text: 'Save',
          cssClass: 'mbsc-popup-button-primary',
        },
      ];
    } else {
      return [
        'cancel',
        {
          handler: () => {
            saveEvent();
          },
          keyCode: 'enter',
          text: 'Add',
          cssClass: 'mbsc-popup-button-primary',
        },
      ];
    }
  }, [isEdit, originalRecurringEvent, saveEvent]);

  const onPopupClose = useCallback(() => {
    setRepeatData(repeatData.filter((item) => item.value !== 'custom-value'));
    if (!isEdit) {
      // Refresh the list, if add popup was canceled, to remove the temporary event
      setMyEvents([...myEvents]);
    }
    setEditFromPopup(false);
    setOpen(false);
  }, [isEdit, myEvents, repeatData]);

  const recurringEditButtons = useMemo<Array<string | MbscPopupButton>>(
    () => [
      'cancel',
      {
        handler: () => {
          if (recurringDelete) {
            deleteRecurringEvent();
          } else {
            const updatedEvent = { ...tempEvent };
            if (editFromPopup) {
              updatedEvent.title = popupEventTitle;
              updatedEvent.description = popupEventDescription;
              updatedEvent.start = popupEventDate[0];
              updatedEvent.end = popupEventDate[1];
              updatedEvent.allDay = popupEventAllDay;
              updatedEvent.recurring = getCustomRule();
            }

            if (recurringEditMode === 'current') {
              delete updatedEvent.id;
              delete updatedEvent.recurring;
              delete updatedEvent.recurringException;
            }

            const events = updateRecurringEvent(
              originalRecurringEvent!,
              eventOccurrence!,
              editFromPopup ? null : newEvent!,
              editFromPopup ? updatedEvent : null,
              recurringEditMode,
            );

            // Update event
            let newEventList = [...myEvents];
            const index = newEventList.findIndex((x) => x.id === events.updatedEvent.id);
            newEventList[index] = events.updatedEvent;

            // Add new event
            if (events.newEvent) {
              newEventList = [...newEventList, events.newEvent];
            }

            setMyEvents(newEventList);

            setOpen(false);
            setRecurringEditOpen(false);
          }
        },
        keyCode: 'enter',
        text: 'Ok',
        cssClass: 'mbsc-popup-button-primary',
      },
    ],
    [
      deleteRecurringEvent,
      editFromPopup,
      eventOccurrence,
      getCustomRule,
      myEvents,
      newEvent,
      originalRecurringEvent,
      popupEventAllDay,
      popupEventDate,
      popupEventDescription,
      popupEventTitle,
      recurringDelete,
      recurringEditMode,
      tempEvent,
    ],
  );

  const recurringEditModeChange = useCallback((ev: ChangeEvent<HTMLInputElement>) => {
    setRecurringEditMode(ev.target.value as 'all' | 'current' | 'following');
  }, []);

  const onRecurringEditClose = useCallback(() => {
    setRecurringEditMode('current');
    setRecurringEditOpen(false);
  }, []);

  return (
    <>
      <Eventcalendar
        view={myView}
        data={myEvents}
        clickToCreate="double"
        dragToCreate={true}
        dragToMove={true}
        dragToResize={true}
        selectedDate={mySelectedDate}
        onSelectedDateChange={onSelectedDateChange}
        onEventClick={onEventClick}
        onEventUpdate={onEventUpdate}
        onEventCreate={onEventCreate}
        onEventCreated={onEventCreated}
        onEventDeleted={onEventDeleted}
        onEventUpdated={onEventUpdated}
      />
      <Popup
        display="bottom"
        contentPadding={false}
        fullScreen={true}
        scrollLock={false}
        height={500}
        headerText={headerText}
        anchor={anchor}
        buttons={popupButtons}
        isOpen={isOpen}
        onClose={onPopupClose}
        responsive={responsivePopup}
        cssClass="md-recurring-event-editor-popup"
      >
        <div className="mbsc-form-group">
          <Input label="Title" value={popupEventTitle} onChange={titleChange} />
          <Textarea label="Description" value={popupEventDescription} onChange={descriptionChange} />
        </div>
        <div className="mbsc-form-group">
          <Switch label="All-day" checked={popupEventAllDay} onChange={allDayChange} />
          <Input ref={startRef} label="Starts" />
          <Input ref={endRef} label="Ends" />
          <Datepicker
            select="range"
            controls={controls}
            touchUi={true}
            startInput={start}
            endInput={end}
            showRangeLabels={false}
            responsive={respSetting}
            onChange={dateChange}
            value={popupEventDate}
          />
          <Select data={repeatData} label="Repeats" value={selectedRepeat} responsive={responsiveSelect} onChange={repeatChange} />
        </div>
        <div className="mbsc-form-group">
          {(selectedRepeat === 'custom' || selectedRepeat === 'custom-value') && (
            <div>
              <div>
                <SegmentedGroup onChange={repeatTypeChange}>
                  <Segmented value="daily" checked={repeatType === 'daily'}>
                    Daily
                  </Segmented>
                  <Segmented value="weekly" checked={repeatType === 'weekly'}>
                    Weekly
                  </Segmented>
                  <Segmented value="monthly" checked={repeatType === 'monthly'}>
                    Monthly
                  </Segmented>
                  <Segmented value="yearly" checked={repeatType === 'yearly'}>
                    Yearly
                  </Segmented>
                </SegmentedGroup>

                <div className="md-recurrence-options">
                  <span>Repeat every</span>
                  <span className="md-recurrence-input md-recurrence-input-nr">
                    <Input min={1} value={repeatNr.toString()} onChange={repeatNrChange} inputStyle="outline" />
                  </span>
                  {repeatType === 'daily' && <span>days</span>}
                  {repeatType === 'weekly' && <span>weeks</span>}
                  {repeatType === 'monthly' && (
                    <span>
                      month(s) on day
                      <span className="md-recurrence-input md-recurrence-input-nr">
                        <Select data={monthlyDays} value={monthlyDay} onChange={monthlyDayChange} inputStyle="outline" />
                      </span>
                    </span>
                  )}
                  {repeatType === 'yearly' && (
                    <span>
                      year(s) <br />
                      on day
                      <span className="md-recurrence-input md-recurrence-input-nr">
                        <Select data={yearlyDays} value={yearlyDay} onChange={yearlyDayChange} inputStyle="outline" />
                      </span>
                      <span>of</span>
                      <span className="md-recurrence-input">
                        <Select data={months} value={selectedMonth} onChange={monthsChange} inputStyle="outline" />
                      </span>
                    </span>
                  )}

                  {repeatType === 'daily' && (
                    <p className="md-recurrence-desc">The event will be repeated every day or every x days, depending on the value</p>
                  )}
                  {repeatType === 'weekly' && (
                    <p className="md-recurrence-desc">The event will be repeated every x weeks on specific weekdays</p>
                  )}
                  {repeatType === 'monthly' && (
                    <p className="md-recurrence-desc">The event will be repeated every x month on specific day of the month</p>
                  )}
                  {repeatType === 'yearly' && (
                    <p className="md-recurrence-desc">The event will be repeated every x years on specific day of a specific month</p>
                  )}
                </div>

                {repeatType === 'weekly' && (
                  <SegmentedGroup select="multiple" onChange={weekDayChange}>
                    <Segmented value="SU" checked={weekDays.indexOf('SU') >= 0}>
                      Sun
                    </Segmented>
                    <Segmented value="MO" checked={weekDays.indexOf('MO') >= 0}>
                      Mon
                    </Segmented>
                    <Segmented value="TU" checked={weekDays.indexOf('TU') >= 0}>
                      Tue
                    </Segmented>
                    <Segmented value="WE" checked={weekDays.indexOf('WE') >= 0}>
                      Wed
                    </Segmented>
                    <Segmented value="TH" checked={weekDays.indexOf('TH') >= 0}>
                      Thu
                    </Segmented>
                    <Segmented value="FR" checked={weekDays.indexOf('FR') >= 0}>
                      Fri
                    </Segmented>
                    <Segmented value="SA" checked={weekDays.indexOf('SA') >= 0}>
                      Sat
                    </Segmented>
                  </SegmentedGroup>
                )}

                <div className="md-recurrence-ends">Ends</div>

                <div className="mbsc-form-group">
                  <RadioGroup>
                    <Radio
                      label="Never"
                      position="start"
                      description="The event will repeat indefinitely"
                      checked={condition === 'never'}
                      onChange={conditionChange}
                      value="never"
                    />
                    <Radio checked={condition === 'until'} onChange={conditionChange} value="until" position="start">
                      Until
                      <span className="md-recurrence-input">
                        <Datepicker
                          inputStyle="outline"
                          controls={['calendar']}
                          display="anchored"
                          touchUi={false}
                          dateFormat="YYYY-MM-DD"
                          returnFormat="iso8601"
                          value={untilDate}
                          onChange={untilDateChange}
                          onOpen={() => setCondition('until')}
                        />
                      </span>
                      <span className="mbsc-description">The event will run until it reaches a specific date</span>
                    </Radio>
                    <Radio checked={condition === 'count'} onChange={conditionChange} value="count" position="start">
                      After
                      <span className="md-recurrence-input md-recurrence-input-nr">
                        <Input
                          inputStyle="outline"
                          value={occurrences.toString()}
                          onChange={occurrancesChange}
                          onClick={() => setCondition('count')}
                        />
                        occurrences
                        <span className="mbsc-description">The event will repeat until it reaches a certain amount of occurrences</span>
                      </span>
                    </Radio>
                  </RadioGroup>
                </div>
              </div>
            </div>
          )}
          {isEdit && (
            <div className="mbsc-button-group">
              <Button className="mbsc-button-block" color="danger" variant="outline" onClick={onDeleteClick}>
                Delete event
              </Button>
            </div>
          )}
        </div>
      </Popup>
      <Popup
        display="bottom"
        contentPadding={false}
        buttons={recurringEditButtons}
        isOpen={isRecurringEditOpen}
        onClose={onRecurringEditClose}
      >
        <div className="mbsc-form-group">
          <div className="mbsc-form-group-title">{recurringText} recurring event</div>
          <RadioGroup onChange={recurringEditModeChange}>
            <Radio label="This event only" checked={recurringEditMode === 'current'} onChange={recurringEditModeChange} value="current" />
            <Radio
              label="This and following events"
              checked={recurringEditMode === 'following'}
              onChange={recurringEditModeChange}
              value="following"
            />
            <Radio label="All events" checked={recurringEditMode === 'all'} onChange={recurringEditModeChange} value="all" />
          </RadioGroup>
        </div>
      </Popup>
    </>
  );
};

export default App;
```

### CSS

```css
.md-recurring-event-editor-popup .md-recurrence-input .mbsc-textfield-wrapper {
  display: inline-block;
  margin: 3px;
  z-index: 3;
  line-height: normal;
}

.md-recurring-event-editor-popup .md-recurrence-input .mbsc-textfield {
  width: 140px;
  height: 32px;
}

.md-recurring-event-editor-popup .md-recurrence-input-nr .mbsc-textfield {
  width: 90px;
}

.md-recurring-event-editor-popup .md-recurrence-input .mbsc-select-icon {
  top: 50%;
  height: 1.5em;
  margin-top: -0.75em;
}

.md-recurrence-options {
  padding: 0 1em;
  margin: 1em 0;
}

.md-recurrence-ends {
  padding: 0 1em;
  margin: 1.25em 0 -1.25em 0;
}

.md-recurrence-desc {
  font-size: 12px;
  font-weight: normal;
  color: #555;
  margin: 1em 0;
}

.md-hide-elm {
  display: none;
}
```

## Implementation notes

- Use `view: { calendar: { labels: true } }` with `clickToCreate: 'double'`, `dragToCreate: true`, `dragToMove: true`, `dragToResize: true`. Pre-load 4 default recurring events: "Take vitamins" (daily every day, 10:00-12:00, `#67ab0d`), "Football training" (weekly Monday, 18:00-20:00, `#fd966a`), "Rent payment" (monthly on day 10, 17:00-18:00, `#3ea39e`), "Dexter BD" (yearly June 6, all-day, `#d00f0f`).
- The **add/edit popup** (`Popup`, responsive: anchored 510px on medium, bottom full-screen on mobile) contains: **Title** (`Input`), **Description** (`Textarea`), **All-day** (`Switch`), **Start/End** (`Datepicker` range with `startInput`/`endInput`; controls: `['calendar', 'time']` normally, `['calendar']` all-day; mobile: `['datetime']` / `['date']`), **Repeats** (`Select`), the **custom recurrence editor** section (shown only when repeat is `'custom'` or `'custom-value'`), and a **Delete event** button (edit mode only).
- The **Repeats `Select`** data has these preset values whose display texts update dynamically based on the event's start date: `norepeat` ("Does not repeat"), `daily` ("Daily"), `weekly` ("Weekly on [day name]"), `monthly` ("Monthly on day [N]"), `monthly-pos` ("Monthly on the [ordinal] [day name]"), `yearly` ("Annually on [Month] [day]"), `yearly-pos` ("Annually on the [ordinal] [day name] of [Month]"), `weekday` ("Every weekday (Monday to Friday)"), `custom` ("Custom"). When an existing event has a custom rule with `interval`/`count`/`until`, a `custom-value` entry with generated descriptive text is inserted dynamically.
- **`loadPopupForm(event)`**: populates all popup fields from the event object; sets the default `untilDate` to the day after `event.start`; rebuilds the Repeats data with date-specific labels. For recurring events: if the rule has `interval`/`count`/`until` set `selectedRepeat = 'custom-value'` and add the generated text entry; if `weekDays === 'MO,TU,WE,TH,FR'` → `'weekday'`; if positional (`pos`) → `repeat + '-pos'`; otherwise → `repeat`. Calls `resetCustomValues()` for non-recurring events.
- **Custom recurrence editor**: a `SegmentedGroup` (Daily/Weekly/Monthly/Yearly) picks `repeatType`. Below it: "Repeat every [interval `Input`] [days/weeks/months/years]". For Weekly: a multi-select weekday `SegmentedGroup` (Sun-Sat). For Monthly: day `Select` (1-31). For Yearly: day `Select` + month `Select`. An **Ends** section with a `RadioGroup`: "Never" / "Until [date `Datepicker`]" / "After [count `Input`] occurrences".
- **`getCustomRule()`**: builds the `recurring` object to save on the event. Presets map directly using the event's start date for weekday/day/month values. `weekday` → `{ repeat: 'weekly', weekDays: 'MO,TU,WE,TH,FR' }`. `custom`/`custom-value` → `{ repeat: repeatType, interval: repeatNr, ...weekDays/day/month as needed, ...until/count from condition }`. Returns `null`/`undefined` for `norepeat`.
- `onEventClick` → open popup in **edit mode**: store `originalRecurringEvent = event.original` if recurring, call `loadPopupForm(event)`, set anchor to clicked element, open with "Edit event" header and "Save" button.
- `onEventCreated` → open popup in **add mode**: call `resetCustomValues()`, `loadPopupForm(event)`, open with "New Event" header and "Add" button. **On popup close without confirming** (add mode): refresh the events array to strip the temporary event; for the imperative API, call `inst.removeEvent(event)` in the popup's `onClose` callback.
- `onEventCreate` → if `args.originEvent` is a recurring event: store `args.event` as `newEvent` and `return false` (prevents duplicate creation; the recurrence mode popup will complete the operation).
- `onEventUpdate` → if the updated event is recurring: store `args.oldEvent` as `originalRecurringEvent` and `args.oldEventOccurrence` as `eventOccurrence`; open the **recurrence mode popup** in "Edit" or "Delete" mode (based on `args.isDelete`); `return false`.
- Handle `onEventDeleted` to delete the event by filtering the events array; for the imperative API, handle deletion in the Delete button's click handler with `inst.removeEvent(editedEvent)`.
- **Recurrence mode popup** (separate `Popup`): header "[Edit/Delete] recurring event"; `RadioGroup`: "This event only" / "This and following events" / "All events" (default: `current`); buttons: Cancel / Ok.
  - **Ok → delete** (`deleteRecurringEvent`): `current` - push `event.start` to `recurringException`, update original; `following` - set `original.recurring.until = event.start`, update original; `all` - remove original. Apply changes to the events array; for the imperative API, call `inst.updateEvent(original)` or `inst.removeEvent(original)`.
  - **Ok → edit**: call `updateRecurringEvent(originalRecurringEvent, eventOccurrence, newEvent, updatedEventOrNull, recurringEditMode)`. React imports it from `@mobiscroll/react`, Vue from `@mobiscroll/vue`, Angular from `@mobiscroll/angular`, JS/jQuery use `mobiscroll.updateRecurringEvent`. Returns `{ updatedEvent, newEvent? }` - replace the original in the list and, if `newEvent` exists, append it; for the imperative API, call `inst.updateEvent(result.updatedEvent)` and `inst.addEvent(result.newEvent)`.
  - When triggered from the **popup Save button** (edit path): pass the form-built event as the 4th argument and `null` as the 3rd. When triggered from **drag/drop** (`onEventUpdate` path): pass `null` as 4th and the drag-created `newEvent` as 3rd.
- **Add button**: build event from form state with `getCustomRule()` as `recurring`, append to the events list; JS/jQuery: call `inst.updateEvent(newEvent)` to finalize the temp event. Then call `navigateTo()` and close popup.
- **Save button (non-recurring)**: build updated event from form state, replace in list, call `navigateTo()`, close popup.
- **`navigateTo()`**: for yearly events, compute the correct year and navigate to `recurring.month`/`recurring.day`; for all others, navigate to `event.start`.

## 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: [Add, Edit and Delete Events](https://demo.mobiscroll.com/react/eventcalendar/create-read-update-delete-CRUD.md) - 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.
- Next: [Block Past Event Creation](https://demo.mobiscroll.com/react/eventcalendar/disallow-past-event-creation.md) - Keep past dates read-only in a React event calendar — block creation and editing on past days and show toast feedback on failed attempts.
