---
title: "React Scheduler Recurring Event Add & Edit Example | Mobiscroll"
component: "Scheduler"
framework: "React"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/react/scheduler/recurring-event-add-edit-dialog"
---

# Recurring Event Add & Edit - React Scheduler demo

Add a recurrence editor to the React Scheduler event dialog — with daily, weekly, monthly, and custom presets plus an RRULE builder. For React and Next.js.

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

## Description

As seen in the [previous example](https://demo.mobiscroll.com/react/scheduler/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/scheduler/recurring-events).

## What this demo shows

- A desktop weekly scheduler layout with a fixed week strip at the top, a fixed all-day row below it, and a scrollable scheduler time grid for the selected week.
- **Header navigation** The month and year label in the top left opens date navigation, while the blue previous and next arrows and the Today button on the right make it easy to move between weeks and jump back to the current day.
- **Week view** The fixed strip below the header displays the days of the selected week, with the current date highlighted by a blue circle.
- **All-day events** All-day events appear in a dedicated row that stays fixed above the time grid.
- **Time grid** The scheduler grid scrolls vertically through the hours of the selected week.
- **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.
- **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 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.

## Best for

- **Recurring event management** Apps that need users to create, edit, and delete repeating events directly from a scheduler 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
- [`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
- [`inputStyle`](https://mobiscroll.com/docs/react/select/api#opt-inputStyle) - option
- [`inputStyle`](https://mobiscroll.com/docs/react/datepicker/api#opt-inputStyle) - 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
- [`inputProps`](https://mobiscroll.com/docs/react/select/api#opt-inputProps) - option
- [`position`](https://mobiscroll.com/docs/react/forms/radio#opt-position) - option
- [`inputProps`](https://mobiscroll.com/docs/react/datepicker/api#opt-inputProps) - 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: '10:00',
    end: '12:00',
    title: 'Take vitamins',
    allDay: false,
    color: '#67ab0d',
    recurring: {
      repeat: 'daily',
      weekDays: 'MO,TU,WE,TH,FR,SA,SU',
    },
  },
  {
    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: '18:00',
    recurring: {
      repeat: 'monthly',
      day: 10,
    },
    title: 'Rent payment',
    allDay: false,
    color: '#3ea39e',
  },
  {
    id: 4,
    recurring: {
      repeat: 'yearly',
      day: 6,
      month: 6,
    },
    title: 'Dexter BD',
    allDay: true,
    color: '#d00f0f',
  },
];

const viewSettings = {
  scheduler: { type: 'week' },
};

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

const selectResponsive = {
  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' };

const dayInputProps = {
  className: 'md-recurrence-input',
  inputStyle: 'outline',
};

const monthInputProps = {
  className: 'md-recurrence-input',
  inputStyle: 'outline',
};

const dateInputProps = {
  className: 'md-recurrence-input',
  inputStyle: 'outline',
};

// Returns the weeknumber of the passed date
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;
  }, [
    editFromPopup,
    popupEventDate,
    tempEvent,
    selectedRepeat,
    repeatType,
    repeatNr,
    condition,
    weekDays,
    monthlyDay,
    yearlyDay,
    selectedMonth,
    untilDate,
    occurrences,
  ]);

  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]);

  // Popuplate data for months
  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],
  );

  // Scheduler options

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

  const handleEventClick = 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 handleEventUpdate = 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 handleEventCreate = useCallback((args) => {
    const originEvent = args.originEvent;
    if (originEvent && originEvent.recurring) {
      setNewEvent(args.event);
      return false;
    }
  }, []);

  const handleEventCreated = 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 handleEventDeleted = useCallback(
    (args) => {
      deleteEvent(args.event);
    },
    [deleteEvent],
  );

  const handleEventUpdated = 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 (
    <div>
      <Eventcalendar
        view={viewSettings}
        data={myEvents}
        clickToCreate="double"
        dragToCreate={true}
        dragToMove={true}
        dragToResize={true}
        selectedDate={mySelectedDate}
        onSelectedDateChange={handleSelectedDateChange}
        onEventClick={handleEventClick}
        onEventUpdate={handleEventUpdate}
        onEventCreate={handleEventCreate}
        onEventCreated={handleEventCreated}
        onEventDeleted={handleEventDeleted}
        onEventUpdated={handleEventUpdated}
      />
      <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={selectResponsive} 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} inputProps={dayInputProps} />
                      </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} inputProps={dayInputProps} />
                      </span>
                      <span>of</span>
                      <span className="md-recurrence-input">
                        <Select data={months} value={selectedMonth} onChange={monthsChange} inputProps={monthInputProps} />
                      </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
                          inputProps={dateInputProps}
                          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>
    </div>
  );
}

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,
  SegmentedGroup,
  SegmentedItem,
  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: '10:00',
    end: '12:00',
    title: 'Take vitamins',
    allDay: false,
    color: '#67ab0d',
    recurring: {
      repeat: 'daily',
      weekDays: 'MO,TU,WE,TH,FR,SA,SU',
    },
  },
  {
    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: '18:00',
    recurring: {
      repeat: 'monthly',
      day: 10,
    },
    title: 'Rent payment',
    allDay: false,
    color: '#3ea39e',
  },
  {
    id: 4,
    recurring: {
      repeat: 'yearly',
      day: 6,
      month: 6,
    },
    title: 'Dexter BD',
    allDay: true,
    color: '#d00f0f',
  },
];

const viewSettings: MbscEventcalendarView = {
  scheduler: {
    type: 'week',
  },
};

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

const selectResponsive: 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' };

const dayInputProps = {
  className: 'custom-repeat-input custom-repeat-select-nr',
  inputStyle: 'outline',
};

const monthInputProps = {
  className: 'custom-repeat-input custom-repeat-select-month',
  inputStyle: 'outline',
};

const dateInputProps = {
  className: 'custom-repeat-input custom-specific-date',
  inputStyle: 'outline',
};

// 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 'current': {
        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;
      }
      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;
    }
    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;
  }, [
    editFromPopup,
    popupEventDate,
    tempEvent,
    selectedRepeat,
    repeatType,
    repeatNr,
    condition,
    weekDays,
    monthlyDay,
    yearlyDay,
    selectedMonth,
    untilDate,
    occurrences,
  ]);

  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()];
            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]);

  // Populate data for months
  const populateMonthDays = useCallback(
    (month: number, type: string) => {
      const newValues = getMonthDays(month);

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

  const repeatChange = useCallback((ev: MbscSelectChangeEvent) => {
    setSelectedRepeat(ev.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((ev: MbscDatepickerChangeEvent) => {
    setUntilDate(ev.value as string);
  }, []);

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

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

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

  const yearlyDayChange = useCallback((ev: MbscSelectChangeEvent) => {
    setYearlyDay(ev.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],
  );

  // Scheduler options

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

  const handleEventClick = useCallback(
    (args: MbscEventClickEvent) => {
      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 handleEventUpdate = 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 handleEventCreate = useCallback((args: MbscEventCreateEvent) => {
    const originEvent = args.originEvent;
    if (originEvent && originEvent.recurring) {
      setNewEvent(args.event);
      return false;
    }
  }, []);

  const handleEventCreated = 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 handleEventDeleted = useCallback(
    (args: MbscEventDeletedEvent) => {
      deleteEvent(args.event);
    },
    [deleteEvent],
  );

  const handleEventUpdated = 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<(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<(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 (
    <div>
      <Eventcalendar
        view={viewSettings}
        data={myEvents}
        clickToCreate="double"
        dragToCreate={true}
        dragToMove={true}
        dragToResize={true}
        selectedDate={mySelectedDate}
        onSelectedDateChange={handleSelectedDateChange}
        onEventClick={handleEventClick}
        onEventUpdate={handleEventUpdate}
        onEventCreate={handleEventCreate}
        onEventCreated={handleEventCreated}
        onEventDeleted={handleEventDeleted}
        onEventUpdated={handleEventUpdated}
      />
      <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={selectResponsive} onChange={repeatChange} />
        </div>
        <div className="mbsc-form-group">
          {(selectedRepeat === 'custom' || selectedRepeat === 'custom-value') && (
            <div>
              <div>
                <SegmentedGroup onChange={repeatTypeChange}>
                  <SegmentedItem value="daily" checked={repeatType === 'daily'}>
                    Daily
                  </SegmentedItem>
                  <SegmentedItem value="weekly" checked={repeatType === 'weekly'}>
                    Weekly
                  </SegmentedItem>
                  <SegmentedItem value="monthly" checked={repeatType === 'monthly'}>
                    Monthly
                  </SegmentedItem>
                  <SegmentedItem value="yearly" checked={repeatType === 'yearly'}>
                    Yearly
                  </SegmentedItem>
                </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} inputProps={dayInputProps} />
                      </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} inputProps={dayInputProps} />
                      </span>
                      <span>of</span>
                      <span className="md-recurrence-input">
                        <Select data={months} value={selectedMonth} onChange={monthsChange} inputProps={monthInputProps} />
                      </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}>
                    <SegmentedItem value="SU" checked={weekDays.indexOf('SU') >= 0}>
                      Sun
                    </SegmentedItem>
                    <SegmentedItem value="MO" checked={weekDays.indexOf('MO') >= 0}>
                      Mon
                    </SegmentedItem>
                    <SegmentedItem value="TU" checked={weekDays.indexOf('TU') >= 0}>
                      Tue
                    </SegmentedItem>
                    <SegmentedItem value="WE" checked={weekDays.indexOf('WE') >= 0}>
                      Wed
                    </SegmentedItem>
                    <SegmentedItem value="TH" checked={weekDays.indexOf('TH') >= 0}>
                      Thu
                    </SegmentedItem>
                    <SegmentedItem value="FR" checked={weekDays.indexOf('FR') >= 0}>
                      Fri
                    </SegmentedItem>
                    <SegmentedItem value="SA" checked={weekDays.indexOf('SA') >= 0}>
                      Sat
                    </SegmentedItem>
                  </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
                          inputProps={dateInputProps}
                          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>
    </div>
  );
};

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: { scheduler: { type: 'week' } }` 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" (repeat indefinitely) / "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: [Bulk Event Actions](https://demo.mobiscroll.com/react/scheduler/event-bulk-actions-edit-delete-update.md) - Add bulk actions to the React Scheduler - select multiple events, then update or delete them in one step, and right-click for single-event actions.
- Next: [Prevent Past Event Creation](https://demo.mobiscroll.com/react/scheduler/disallow-past-event-creation.md) - Block event creation and editing on past dates in the React Scheduler — past dates are blocked, past events are locked, and failed attempts show a toast.
