---
title: "React Event calendar Drag-and-Drop Task Presets Example | Mobiscroll"
component: "Event calendar"
framework: "React"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/react/eventcalendar/external-event-presets"
---

# Drag-and-Drop Task Presets - React Event calendar demo

Drag preset task templates from an external list onto a React event calendar — a popup form captures task details and technician at drop time. For React and Next.js.

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

## Description

Use external draggable events to create preset tasks that people can quickly copy and spawn events from. A great example is a car wrapping shop where different jobs need to be set up depending on the type of vehicle.

Print a list of predefined tasks and initialize them as `draggable` elements. Enable [`externalDrop`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrop) for the calendar and set up the predefined event skeletons for the [draggable](https://mobiscroll.com/docs/react/eventcalendar/drag-and-drop#targeting-the-eventcalendar) component.

Whenever there is more information to be captured, like detailed task notes, customer information, the name of the technician... you can trigger a `popup` with the task form in the [`onEventCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreate) lifecycle event. For failed drops (trying to schedule a task during lunch breaks or weekends) the [`onEventCreateFailed`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreateFailed) event will be triggered.

**Want to see how external presets look & feel for a scheduler?** [Check out this example →](https://demo.mobiscroll.com/react/scheduler/external-event-presets)

## What this demo shows

- A desktop month view event calendar paired with an external list of predefined tasks that can be dragged onto the calendar.
- **Calendar layout** The main view shows a full month grid with no events.
- **Calendar header** The header shows the current month and year on the left, and blue month navigation arrows with a `Today` button between them on the right.
- **Invalid dates** Weekend days are disabled, so they cannot be used as drop targets for new tasks. When a user tries to drop an event in a restricted cell, a `Can't create event on this date` toast appears at the bottom center of the calendar.
- **External task list** A right-side panel titled `Available tasks` displays preset items: `Small wrap 2 day`, `Full-size wrap 3 days`, `Mid-size wrap 3 days`, `Roadster wrap 3 days`, `SUV wrap 4 days`, and `Hypercar wrap 5 days`.
- **External drag & drop** Tasks can be dragged from the external list and dropped onto a calendar day to schedule predefined events.
- **Created event display** Dropping the task opens a popup below the event label with the title `Assign task`.
- **Popup form** The popup includes a `Task` text field prefilled with the task title, a `Details` text field with the `Add description…` placeholder, and a `Technician` select with the `Please select…` placeholder.
- **Popup confirmation** The popup footer contains a blue `Ok` button in the bottom-right corner to confirm the event details.
- **Scheduled task** After a successful drop and confirmation the task label appears inside day cell and a confirmation toast appears on the bottom center part of the calendar with this message `New task added`.
- **Day cell states** Hovering a day cell highlights the day number with a gray background, while clicking the empty part of the cell selects the day and highlights the day number with a blue background.
- **Gesture navigation** The month view can also be changed by clicking and dragging the calendar left or right.

## Best for

- **Service scheduling** Planning repeatable job types, such as vehicle wrapping tasks, from predefined task templates.
- **Template-based booking** Letting teams create new calendar events by dragging standard task presets onto available dates.
- **Technician assignment workflows** Capturing extra scheduling details, such as notes and technician selection, at the moment a task is placed.

## 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
- [`data`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-data) - option
- [`labels`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-labels) - option
- [`view`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-view) - option
- [`invalid`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-invalid) - option
- [`dragToMove`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-dragToMove) - option
- [`externalDrop`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrop) - option
- [`onEventCreated`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreated) - event
- [`onEventCreateFailed`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreateFailed) - event
- [`onEventUpdateFailed`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventUpdateFailed) - event
- [`display`](https://mobiscroll.com/docs/react/popup/api#opt-display) - option
- [`width`](https://mobiscroll.com/docs/react/popup/api#opt-width) - option
- [`touchUi`](https://mobiscroll.com/docs/react/popup/api#opt-touchUi) - option
- [`headerText`](https://mobiscroll.com/docs/react/popup/api#opt-headerText) - option
- [`buttons`](https://mobiscroll.com/docs/react/popup/api#opt-buttons) - option
- [`anchor`](https://mobiscroll.com/docs/react/popup/api#opt-anchor) - option
- [`isOpen`](https://mobiscroll.com/docs/react/popup/api#opt-isOpen) - option
- [`onClose`](https://mobiscroll.com/docs/react/popup/api#event-onClose) - event
- [`data`](https://mobiscroll.com/docs/react/select/api#opt-data) - option
- [`onChange`](https://mobiscroll.com/docs/react/select/api#event-onChange) - event
- [`display`](https://mobiscroll.com/docs/react/select/api#opt-display) - option
- [`touchUi`](https://mobiscroll.com/docs/react/select/api#opt-touchUi) - option
- [`label`](https://mobiscroll.com/docs/react/select/api#opt-label) - option
- [`inputProps`](https://mobiscroll.com/docs/react/select/api#opt-inputProps) - option
- [`onClose`](https://mobiscroll.com/docs/react/notifications/toast#event-onClose) - event

## Code

### JSX

```jsx
import { Draggable, Eventcalendar, Input, Popup, Select, setOptions, Textarea, Toast } from '@mobiscroll/react';
import PropTypes from 'prop-types';
import { useCallback, useMemo, useState } from 'react';

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

function Task({ data }) {
  const [draggable, setDraggable] = useState();

  return (
    <div ref={setDraggable} style={{ background: data.color }} className="external-event-task">
      <div>{data.title}</div>
      <div>{data.length}</div>
      <Draggable dragData={data} element={draggable} />
    </div>
  );
}

Task.propTypes = {
  data: PropTypes.object.isRequired,
};

function App() {
  const [isOpen, setOpen] = useState(false);
  const [title, setTitle] = useState('');
  const [details, setDetails] = useState('');
  const [technician, setTechnician] = useState();
  const [anchor, setAnchor] = useState(null);
  const [isToastOpen, setToastOpen] = useState(false);
  const [toastMessage, setToastMessage] = useState();

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

  const myTasks = useMemo(
    () => [
      {
        title: 'Small wrap',
        color: '#637e57',
        start: '2026-11-07T00:00',
        end: '2026-11-08T00:00',
        length: '2 days',
      },
      {
        title: 'Full-size wrap',
        color: '#50789d',
        start: '2026-11-07T00:00',
        end: '2026-11-09T00:00',
        length: '3 days',
      },
      {
        title: 'Mid-size wrap',
        color: '#6c5d45',
        start: '2026-11-07T00:00',
        end: '2026-11-09T00:00',
        length: '3 days',
      },
      {
        title: 'Roadster wrap',
        color: '#9da721',
        start: '2026-11-07T00:00',
        end: '2026-11-09T00:00',
        length: '3 days',
      },
      {
        title: 'SUV wrap',
        color: '#cd6957',
        start: '2026-11-07T00:00',
        end: '2026-11-10T00:00',
        length: '4 days',
      },
      {
        title: 'Hypercar wrap',
        color: '#7a5886',
        start: '2026-11-07T00:00',
        end: '2026-11-11T00:00',
        length: '5 days',
      },
    ],
    [],
  );

  const myData = useMemo(
    () => [
      { value: '1', text: 'Roly Chester' },
      { value: '2', text: 'Tucker Wayne' },
      { value: '3', text: 'Baker Brielle' },
      { value: '4', text: 'Jami Walter' },
      { value: '5', text: 'Patrick Toby' },
      { value: '6', text: 'Tranter Logan' },
      { value: '7', text: 'Payton Sinclair' },
    ],
    [],
  );

  const myInvalid = useMemo(
    () => [
      {
        recurring: {
          repeat: 'weekly',
          weekDays: 'SA,SU',
        },
      },
    ],
    [],
  );

  const handleEventCreated = useCallback((args) => {
    setTitle(args.event.title);
    setDetails(args.event.details);
    setTechnician(args.event.technician);
    setAnchor(args.target);
    setOpen(true);
  }, []);

  const handleEventUpdateFailed = useCallback(() => {
    setToastMessage("Can't create event on this date");
    setToastOpen(true);
  }, []);

  const handlePopupClose = useCallback(() => {
    setOpen(false);
    setToastMessage('New task added');
    setToastOpen(true);
  }, []);

  const handleSelectChange = useCallback((event) => {
    setTechnician(event.value);
  }, []);

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

  return (
    <div className="mbsc-grid mbsc-no-padding">
      <div className="mbsc-row">
        <div className="mbsc-col-sm-9 external-event-calendar">
          <Eventcalendar
            view={myView}
            invalid={myInvalid}
            dragToMove={true}
            externalDrop={true}
            onEventCreated={handleEventCreated}
            onEventCreateFailed={handleEventUpdateFailed}
            onEventUpdateFailed={handleEventUpdateFailed}
          />
        </div>
        <div className="mbsc-col-sm-3">
          <div className="mbsc-form-group-title">Available tasks</div>
          {myTasks.map((task, i) => (
            <Task key={i} data={task} />
          ))}
        </div>
        <Popup
          display="anchored"
          width={400}
          contentPadding={false}
          touchUi={false}
          headerText="Assign task"
          buttons={['ok']}
          anchor={anchor}
          isOpen={isOpen}
          onClose={handlePopupClose}
        >
          <div className="mbsc-form-group">
            <Input label="Task" defaultValue={title} readOnly></Input>
            <Textarea label="Details" defaultValue={details} placeholder="Add description..."></Textarea>
            <Select
              data={myData}
              value={technician}
              onChange={handleSelectChange}
              display="anchored"
              touchUi={false}
              label="Technician"
              inputProps={{ placeholder: 'Please select...' }}
            />
          </div>
        </Popup>
      </div>
      <Toast message={toastMessage} isOpen={isToastOpen} onClose={handleToastClose} />
    </div>
  );
}

export default App;
```

### TSX

```tsx
import {
  Draggable,
  Eventcalendar,
  Input,
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscEventCreatedEvent,
  MbscSelectChangeEvent,
  Popup,
  Select,
  setOptions,
  Textarea,
  Toast,
} from '@mobiscroll/react';
import { FC, useCallback, useMemo, useState } from 'react';

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

const myTasks = [
  {
    title: 'Small wrap',
    color: '#637e57',
    start: '2026-11-07T00:00',
    end: '2026-11-08T00:00',
    length: '2 days',
  },
  {
    title: 'Full-size wrap',
    color: '#50789d',
    start: '2026-11-07T00:00',
    end: '2026-11-09T00:00',
    length: '3 days',
  },
  {
    title: 'Mid-size wrap',
    color: '#6c5d45',
    start: '2026-11-07T00:00',
    end: '2026-11-09T00:00',
    length: '3 days',
  },
  {
    title: 'Roadster wrap',
    color: '#9da721',
    start: '2026-11-07T00:00',
    end: '2026-11-09T00:00',
    length: '3 days',
  },
  {
    title: 'SUV wrap',
    color: '#cd6957',
    start: '2026-11-07T00:00',
    end: '2026-11-10T00:00',
    length: '4 days',
  },
  {
    title: 'Hypercar wrap',
    color: '#7a5886',
    start: '2026-11-07T00:00',
    end: '2026-11-11T00:00',
    length: '5 days',
  },
];

const myData = [
  { value: '1', text: 'Roly Chester' },
  { value: '2', text: 'Tucker Wayne' },
  { value: '3', text: 'Baker Brielle' },
  { value: '4', text: 'Jami Walter' },
  { value: '5', text: 'Patrick Toby' },
  { value: '6', text: 'Tranter Logan' },
  { value: '7', text: 'Payton Sinclair' },
];

function Task(props: { data: MbscCalendarEvent }) {
  const [draggable, setDraggable] = useState<HTMLDivElement>();

  const setDragElm = useCallback((elm: HTMLDivElement) => {
    setDraggable(elm);
  }, []);

  return (
    <div ref={setDragElm} style={{ background: props.data.color }} className="external-event-task">
      <div>{props.data.title}</div>
      <div>{props.data.length}</div>
      <Draggable dragData={props.data} element={draggable} />
    </div>
  );
}

const App: FC = () => {
  const [isOpen, setOpen] = useState<boolean>(false);
  const [title, setTitle] = useState<string>('');
  const [details, setDetails] = useState<string>('');
  const [technician, setTechnician] = useState<string>('');
  const [anchor, setAnchor] = useState<HTMLElement>();
  const [isToastOpen, setToastOpen] = useState<boolean>(false);
  const [toastMessage, setToastMessage] = useState<string>();

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

  const myInvalid = useMemo(
    () => [
      {
        recurring: {
          repeat: 'weekly',
          weekDays: 'SA,SU',
        },
      },
    ],
    [],
  );

  const fillDialog = useCallback((args: MbscEventCreatedEvent) => {
    setTitle(args.event.title!);
    setDetails(args.event.details);
    setTechnician(args.event.technician);
    setAnchor(args.target!);
    setOpen(true);
  }, []);

  const handleEventCreated = useCallback(
    (args: MbscEventCreatedEvent) => {
      fillDialog(args);
    },
    [fillDialog],
  );

  const handleEventUpdateFailed = useCallback(() => {
    setToastMessage("Can't create event on this date");
    setToastOpen(true);
  }, []);

  const handlePopupClose = useCallback(() => {
    setOpen(false);
    setToastMessage('New task added');
    setToastOpen(true);
  }, []);

  const handleSelectChange = useCallback((event: MbscSelectChangeEvent) => {
    setTechnician(event.value);
  }, []);

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

  return (
    <div className="mbsc-grid mbsc-no-padding">
      <div className="mbsc-row">
        <div className="mbsc-col-sm-9 external-event-calendar">
          <Eventcalendar
            view={myView}
            invalid={myInvalid}
            dragToMove={true}
            externalDrop={true}
            onEventCreated={handleEventCreated}
            onEventCreateFailed={handleEventUpdateFailed}
            onEventUpdateFailed={handleEventUpdateFailed}
          />
        </div>
        <div className="mbsc-col-sm-3">
          <div className="mbsc-form-group-title">Available tasks</div>
          {myTasks.map((task, i) => (
            <Task key={i} data={task} />
          ))}
        </div>
        <Popup
          display="anchored"
          width={400}
          contentPadding={false}
          touchUi={false}
          headerText="Assign task"
          buttons={['ok']}
          anchor={anchor}
          isOpen={isOpen}
          onClose={handlePopupClose}
        >
          <div className="mbsc-form-group">
            <Input label="Task" defaultValue={title} readOnly></Input>
            <Textarea label="Details" defaultValue={details} placeholder="Add description..."></Textarea>
            <Select
              data={myData}
              value={technician}
              onChange={handleSelectChange}
              display="anchored"
              touchUi={false}
              label="Technician"
              inputProps={{ placeholder: 'Please select...' }}
            />
          </div>
        </Popup>
      </div>
      <Toast message={toastMessage} isOpen={isToastOpen} onClose={handleToastClose} />
    </div>
  );
};
export default App;
```

### CSS

```css
.external-event-calendar {
  border-right: 1px solid #ccc;
}

.external-event-task {
  color: #fff;
  padding: 10px;
  margin: 20px;
  border-radius: 8px;
  font-family:
    -apple-system,
    Segoe UI,
    Roboto,
    sans-serif;
}

.demo-external-event-presets.demo-wrapper,
.demo-external-event-presets .mbsc-grid,
.demo-external-event-presets .mbsc-row,
.demo-external-event-presets .external-event-calendar {
  height: 100%;
}
```

## Implementation notes

- Set `view: { calendar: { labels: true } }`. Enable `externalDrop: true` to accept drops from outside the calendar and `dragToMove: true` to allow moving already-placed events. Block weekends with `invalid: [{ recurring: { repeat: 'weekly', weekDays: 'SA,SU' } }]`.
- Define each preset task as an object with `title`, `color`, `start`, and `end` fields (where `start`/`end` represent today-relative offsets that set the event duration when dropped). Render the task list outside the calendar and initialize each item as a draggable element using the `Draggable` component with `dragData` set to the task object. Angular: use the `mbsc-draggable` directive with `[dragData]`. JS/jQuery: call `mobiscroll.draggable(element, { dragData: task })` for each task element.
- Handle `onEventCreated` (Vue: `@event-created`) after a successful drop: open an anchored `Popup` targeting `args.target` with `display="anchored"`. The popup contains a read-only `Input` prefilled with `args.event.title`, a `Textarea` for details, and a `Select` for technician assignment, with an `['ok']` button to confirm. Show a `Toast` with `'New task added'` when the popup closes. Angular: use `@ViewChild` to get the `MbscPopup` instance and call `.open()` imperatively.
- Handle `onEventCreateFailed` and `onEventUpdateFailed` (Vue: `@event-create-failed`, `@event-update-failed`) to show a `Toast` when a drop lands on a weekend or other invalid date.

## 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: [Double Booking Prevention](https://demo.mobiscroll.com/react/eventcalendar/prevent-double-booking-events.md) - Prevent overlapping events in a React event calendar — block double bookings per event or across all events with in-calendar conflict alerts.
- Next: [Drag and Drop from External Lists](https://demo.mobiscroll.com/react/eventcalendar/external-drag-drop-sortable-dragula.md) - Drag items from external SortableJS or Dragula lists onto a React event calendar to schedule events — or drag them out to unschedule.
