---
title: "React Scheduler External Drag and Drop Example | Mobiscroll"
component: "Scheduler"
framework: "React"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/react/scheduler/external-drag-drop-sortable-dragula"
---

# External Drag and Drop - React Scheduler demo

Connect SortableJS or Dragula lists to the React Scheduler — drag items in to schedule events and drag events out to unschedule them. For React and Next.JS.

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

## Description

**Dragging events on the Mobiscroll event calendar**

Events can be scheduled by dragging and dropping an external item onto the scheduler with Mobiscroll `draggable`. In order for that to work you will need to have two things set up:

- Enable the scheduler to receive external events by setting [`externalDrop`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrop) to `true`.

- Initialize the external events (containers) as `draggable` components.

Use the `Draggable` component to specify a skeleton event through its `dragData` option and reference the draggable container in the `element` option. The `dragData` accepts a full event definition that will be added to the event calendar on drop. If omitted, a default event will be created.

**Working together with SortableJS and Dragula lists**

Mobiscroll provides built-in support for two widely used reorderable drag-and-drop list libraries: [SortableJS](https://sortablejs.github.io/Sortable/) and [Dragula](https://bevacqua.github.io/dragula/). If you are already using any of these libraries to reorder list items, they can be seamlessly integrated with the scheduler. This integration enables a smooth workflow where items can be dragged from external lists and scheduled directly onto the calendar.

Events can be created and scheduled by dragging items from third-party lists into the event calendar. To enable this behavior you will need to:

- Allow the scheduler to receive external drops by setting [`externalDrop`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrop).
- Use the `sortableJsDraggable` or `dragulaDraggable` plugins to connect the third-party list with the scheduler.

The event data will be read from the dragged element's `data-drag-data` attribute, or the plugin's options expose an `eventData` function where the event definition (title, start, end, resource, etc.) can be specified.

You can then handle the calendars lifecycle events such as [`onEventCreate`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreate), [`onEventCreated`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreated) or [`onEventCreateFailed`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreateFailed) to run custom logic (e.g. showing a toast) when a drop succeeds or fails.

**Unscheduling events (or dragging them off a calendar)**

Additionally events can be unscheduled by dragging them out from the scheduler and dropping them onto an external drop container. To activate this, you will need to:

- Enable the scheduler to allow dragging events out by setting [`externalDrag`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrag) to `true`.

- Initialize the [`dropcontainer`](https://mobiscroll.com/docs/react/eventcalendar/drag-and-drop#dropcontainer).

Use the [`dropcontainer`](https://mobiscroll.com/docs/react/eventcalendar/drag-and-drop#dropcontainer) component and its [`onItemDrop`](https://mobiscroll.com/docs/react/eventcalendar/drag-and-drop#event-onItemDrop) event to handle dropped events.

Events can also be dropped on another event calendar with `externalDrop` enabled.

When an event is dropped into an external drop container or another calendar, it will be deleted from the original calendar and the [`onEventDelete`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventDelete) will be fired.

You can also use the [`onEventDragLeave`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventDragLeave) and [`onEventDragEnter`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventDragEnter), and the drop container's `onItemDragEnter` and `onItemDragLeave` events to provide visual feedback or running custom logic during drag.

Learn more from the [external drag & drop documentation](https://mobiscroll.com/docs/react/eventcalendar/drag-and-drop#the-eventcalendar-as-source).

**Unscheduling events to SortableJS and Dragula lists**

Dropping events from the Eventcalendar to [SortableJS](https://sortablejs.github.io/Sortable/) and [Dragula](https://bevacqua.github.io/dragula/) lists is also possible with the `sortableJsDraggable` and `dragulaDraggable` plugins. To enable this behavior you will need to set the following under the `options` configuration:

- Allow the list to receive external drops by setting `externalDrop` to `true`
- Use the `onExternalDrop` callback to update the content of the third-party list. The function arguments return the `afterElement` - the list element before which the clone is dropped,

`container` - the list container, `dragData` - the dragged event data, `position` - the index where the clone is dropped on the list.

For details, and option lists see the [`externalDrop`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrop) section in the documentation

**Looking for external drag into a calendar?** [Check out this example →](https://demo.mobiscroll.com/react/eventcalendar/external-drag-drop-sortable-dragula)

## 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.
- **External lists** A scrollable panel on the right contains three external event lists: `Mobiscroll draggable` with four red items, `SortableJS list (externally sortable)` with four yellow items, and `Dragula list (externally sortable)` with four green items.
- **Scheduling from lists** Any item from the external lists can be dragged onto the scheduler to create and schedule that event on the target time.
- **Unscheduling back to lists** Scheduled events can be dragged out of the scheduler and dropped back into an external list, removing them from the week view.
- **List behavior** When an item is scheduled from an external list, it disappears from that list and appears on the scheduler.
- **Feedback on drop** After an event is scheduled from an external list, a toast appears near the bottom center of the scheduler showing the event title and the `added` meesage.
- **Feedback on externap list drop** After an event is unscheduled from the scheduler and dropped on the external list a toast appears near the bottom center of the scheduler showing the event title and the `unscheduled` message.
- **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 selection** Clicking an event selects and highlights it.
- **Event creation** Double-clicking the time grid or clicking and dragging on it creates a new event.

## Best for

- **Unscheduled work queues** Managing tasks, appointments, or work items that start in an external backlog and need to be placed onto specific times.
- **Desktop planning workflows** Weekly scheduler experiences where users need a time-grid view together with a side panel of draggable items.
- **Schedule and unschedule flows** Use cases where users assign items to the scheduler, then move them back out to return them to an unscheduled state.
- **Third-party drag-and-drop integration** Projects that combine Mobiscroll Eventcalendar with existing `SortableJS` or `Dragula` list implementations.

## 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
- [`externalDrop`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrop) - 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
- [`eventDelete`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-eventDelete) - option
- [`view`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-view) - option
- [`externalDrag`](https://mobiscroll.com/docs/react/eventcalendar/api#opt-externalDrag) - option
- [`onEventCreated`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventCreated) - event
- [`onEventDeleted`](https://mobiscroll.com/docs/react/eventcalendar/api#event-onEventDeleted) - event
- [`onClose`](https://mobiscroll.com/docs/react/notifications/toast#event-onClose) - event

## Code

### JSX

```jsx
import {
  Draggable,
  dragulaDraggable,
  Dropcontainer,
  Eventcalendar,
  getJson,
  setOptions,
  sortableJsDraggable,
  Toast,
} from '@mobiscroll/react';
import dragula from 'dragula';
import PropTypes from 'prop-types';
import { useCallback, useEffect, useMemo, useState } from 'react';
import Sortable from 'sortablejs';
import 'dragula/dist/dragula.css';

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

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

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

  const eventLength = Math.round(Math.abs(new Date(data.end).getTime() - new Date(data.start).getTime()) / (60 * 60 * 1000));

  return (
    <div
      className="mds-drag-drop-sort-task"
      ref={isDraggable && setDragElm}
      style={{ background: data.color }}
      data-drag-data={!isDraggable && JSON.stringify(data)}
    >
      <div>{data.title}</div>
      <div>{eventLength + ' hour' + (eventLength > 1 ? 's' : '')}</div>
      {isDraggable && <Draggable dragData={data} element={draggable} />}
    </div>
  );
}

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

function App() {
  const [dropCont, setDropCont] = useState();
  const [sortableCont, setSortableCont] = useState();
  const [dragulaCont, setDragulaCont] = useState();
  const [myEvents, setEvents] = useState([]);
  const [isToastOpen, setToastOpen] = useState(false);
  const [toastMessage, setToastMessage] = useState();

  const [myDraggableTasks, setDraggableTasks] = useState([
    {
      id: 1,
      title: 'Task 1',
      color: '#cf4343',
      start: '2026-10-23T08:00',
      end: '2026-10-23T09:30',
    },
    {
      id: 2,
      title: 'Task 2',
      color: '#cf4343',
      start: '2026-10-23T08:00',
      end: '2026-10-23T10:00',
    },
    {
      id: 3,
      title: 'Task 3',
      color: '#cf4343',
      start: '2026-10-23T10:00',
      end: '2026-10-23T14:00',
    },
    {
      id: 4,
      title: 'Task 4',
      color: '#cf4343',
      start: '2026-10-23T12:00',
      end: '2026-10-23T18:00',
    },
  ]);

  const [mySortableTasks, setSortableTasks] = useState([
    {
      id: 'sortable-1',
      title: 'Task 5',
      color: '#e49516',
      start: '2026-10-23T08:00',
      end: '2026-10-23T09:30',
    },
    {
      id: 'sortable-2',
      title: 'Task 6',
      color: '#e49516',
      start: '2026-10-23T12:00',
      end: '2026-10-23T15:00',
    },
    {
      id: 'sortable-3',
      title: 'Task 7',
      color: '#e49516',
      start: '2026-10-23T08:30',
      end: '2026-10-23T11:00',
    },
    {
      id: 'sortable-4',
      title: 'Task 8',
      color: '#e49516',
      start: '2026-10-23T16:00',
      end: '2026-10-23T17:00',
    },
  ]);

  const [myDragulaTasks, setDragulaTasks] = useState([
    {
      id: 'dragula-1',
      title: 'Task 9',
      color: '#1ca11a',
      start: '2026-10-23T08:00',
      end: '2026-10-23T09:30',
    },
    {
      id: 'dragula-2',
      title: 'Task 10',
      color: '#1ca11a',
      start: '2026-10-23T12:00',
      end: '2026-10-23T15:00',
    },
    {
      id: 'dragula-3',
      title: 'Task 11',
      color: '#1ca11a',
      start: '2026-10-23T08:30',
      end: '2026-10-23T11:00',
    },
    {
      id: 'dragula-4',
      title: 'Task 12',
      color: '#1ca11a',
      start: '2026-10-23T16:00',
      end: '2026-10-23T17:00',
    },
  ]);

  const myView = useMemo(() => ({ scheduler: { type: 'week' } }), []);

  const handleEventCreated = useCallback((args) => {
    if (args.action === 'externalDrop') {
      setDraggableTasks((tasks) => tasks.filter((item) => item.id !== args.event.id));
      setSortableTasks((tasks) => tasks.filter((item) => item.id !== args.event.id));
      setDragulaTasks((tasks) => tasks.filter((item) => item.id !== args.event.id));
      setToastMessage(args.event.title + ' added');
      setToastOpen(true);
    }
  }, []);

  const handleEventDeleted = useCallback((args) => {
    setToastMessage(args.event.title + ' unscheduled');
    setToastOpen(true);
  }, []);

  const handleItemDrop = useCallback((args) => {
    if (args.data) {
      setDraggableTasks((tasks) => [...tasks, args.data]);
    }
  }, []);

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

  useEffect(() => {
    let sortableInstance;
    if (sortableCont) {
      sortableInstance = new Sortable(sortableCont, {
        animation: 150,
        forceFallback: true,
      });

      sortableJsDraggable.init(sortableInstance, {
        cloneSelector: '.sortable-drag',
        externalDrop: true,
        onExternalDrop: (a) => {
          const dragData = a.dragData;
          setSortableTasks((prev) => {
            const newTasks = [...prev];
            newTasks.splice(a.position, 0, dragData);
            return newTasks;
          });
        },
      });
    }

    let drake;
    if (dragulaCont) {
      drake = dragula([dragulaCont]);
      dragulaDraggable.init(drake, {
        externalDrop: true,
        onExternalDrop: (a) => {
          const dragData = a.dragData;
          setDragulaTasks((prev) => {
            const newTasks = [...prev];
            newTasks.splice(a.position, 0, dragData);
            return newTasks;
          });
        },
      });
    }

    return () => {
      if (sortableInstance) {
        sortableInstance.destroy();
      }
      if (drake) {
        drake.destroy();
      }
    };
  }, [dragulaCont, sortableCont]);

  useEffect(() => {
    getJson(
      'https://trial.mobiscroll.com/drag-drop-events/',
      (events) => {
        setEvents(events);
      },
      'jsonp',
    );
  }, []);

  return (
    <div className="mbsc-grid mbsc-no-padding mds-full-height">
      <div className="mbsc-row mds-full-height">
        <div className="mbsc-col-sm-9 mds-drag-drop-sort-calendar mds-full-height">
          <Eventcalendar
            clickToCreate={true}
            dragToCreate={true}
            dragToMove={true}
            dragToResize={true}
            eventDelete={true}
            data={myEvents}
            view={myView}
            externalDrop={true}
            dragToMove={true}
            dragToCreate={true}
            externalDrag={true}
            onEventCreated={handleEventCreated}
            onEventDeleted={handleEventDeleted}
          />
        </div>
        <div className="mbsc-col-sm-3 mds-drag-drop-sort-container-wrapper mds-full-height">
          <div className="mbsc-txt-muted mds-third-party-title">Mobiscroll draggable</div>
          <div className="mds-drag-drop-sort-container" ref={setDropCont}>
            <Dropcontainer onItemDrop={handleItemDrop} element={dropCont}>
              {myDraggableTasks.map((task) => (
                <Task key={task.id} data={task} isDraggable={true} />
              ))}
            </Dropcontainer>
          </div>
          <div className="mbsc-txt-muted mds-third-party-title">SortableJS list (externally sortable)</div>
          <div className="mds-drag-drop-sort-container" ref={setSortableCont}>
            {mySortableTasks.map((task) => (
              <Task key={task.id} data={task} />
            ))}
          </div>
          <div className="mbsc-txt-muted mds-third-party-title">Dragula list (externally sortable)</div>
          <div className="mds-drag-drop-sort-container" ref={setDragulaCont}>
            {myDragulaTasks.map((task) => (
              <Task key={task.id} data={task} />
            ))}
          </div>
        </div>
      </div>
      <Toast message={toastMessage} isOpen={isToastOpen} onClose={handleToastClose} />
    </div>
  );
}

export default App;
```

### TSX

```tsx
import {
  Draggable,
  dragulaDraggable,
  Dropcontainer,
  Eventcalendar,
  getJson,
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscEventCreatedEvent,
  MbscEventDeletedEvent,
  MbscExternalDropEvent,
  MbscItemDragEvent,
  setOptions,
  sortableJsDraggable,
  Toast,
} from '@mobiscroll/react';
import dragula, { Drake } from 'dragula';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import Sortable from 'sortablejs';
import 'dragula/dist/dragula.css';
setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

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

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

  const eventLength = Math.round(
    Math.abs(new Date(data.end as string).getTime() - new Date(data.start as string).getTime()) / (60 * 60 * 1000),
  );

  return (
    <div
      className="mds-drag-drop-sort-task"
      ref={isDraggable ? setDragElm : null}
      style={{ background: data.color }}
      data-drag-data={!isDraggable && JSON.stringify(data)}
    >
      <div>{data.title}</div>
      <div>{eventLength + ' hour' + (eventLength > 1 ? 's' : '')}</div>
      {isDraggable && <Draggable dragData={data} element={draggable} />}
    </div>
  );
};

const App: FC = () => {
  const [dropCont, setDropCont] = useState<HTMLDivElement | null>();
  const [sortableCont, setSortableCont] = useState<HTMLDivElement | null>();
  const [dragulaCont, setDragulaCont] = useState<HTMLDivElement | null>();
  const [myEvents, setEvents] = useState<MbscCalendarEvent[]>([]);
  const [isToastOpen, setToastOpen] = useState<boolean>(false);
  const [toastMessage, setToastMessage] = useState<string>('');

  const [myDraggableTasks, setDraggableTasks] = useState<MbscCalendarEvent[]>([
    {
      id: 1,
      title: 'Task 1',
      color: '#cf4343',
      start: '2026-10-23T08:00',
      end: '2026-10-23T09:30',
    },
    {
      id: 2,
      title: 'Task 2',
      color: '#cf4343',
      start: '2026-10-23T08:00',
      end: '2026-10-23T10:00',
    },
    {
      id: 3,
      title: 'Task 3',
      color: '#cf4343',
      start: '2026-10-23T10:00',
      end: '2026-10-23T14:00',
    },
    {
      id: 4,
      title: 'Task 4',
      color: '#cf4343',
      start: '2026-10-23T12:00',
      end: '2026-10-23T18:00',
    },
  ]);

  const [mySortableTasks, setSortableTasks] = useState<MbscCalendarEvent[]>([
    {
      id: 'sortable-1',
      title: 'Task 5',
      color: '#e49516',
      start: '2026-10-23T08:00',
      end: '2026-10-23T09:30',
    },
    {
      id: 'sortable-2',
      title: 'Task 6',
      color: '#e49516',
      start: '2026-10-23T12:00',
      end: '2026-10-23T15:00',
    },
    {
      id: 'sortable-3',
      title: 'Task 7',
      color: '#e49516',
      start: '2026-10-23T08:30',
      end: '2026-10-23T11:00',
    },
    {
      id: 'sortable-4',
      title: 'Task 8',
      color: '#e49516',
      start: '2026-10-23T16:00',
      end: '2026-10-23T17:00',
    },
  ]);

  const [myDragulaTasks, setDragulaTasks] = useState<MbscCalendarEvent[]>([
    {
      id: 'dragula-1',
      title: 'Task 9',
      color: '#1ca11a',
      start: '2026-10-23T08:00',
      end: '2026-10-23T09:30',
    },
    {
      id: 'dragula-2',
      title: 'Task 10',
      color: '#1ca11a',
      start: '2026-10-23T12:00',
      end: '2026-10-23T15:00',
    },
    {
      id: 'dragula-3',
      title: 'Task 11',
      color: '#1ca11a',
      start: '2026-10-23T08:30',
      end: '2026-10-23T11:00',
    },
    {
      id: 'dragula-4',
      title: 'Task 12',
      color: '#1ca11a',
      start: '2026-10-23T16:00',
      end: '2026-10-23T17:00',
    },
  ]);

  const myView = useMemo<MbscEventcalendarView>(() => ({ scheduler: { type: 'week' } }), []);

  const handleEventCreated = useCallback((args: MbscEventCreatedEvent) => {
    if (args.action === 'externalDrop') {
      setDraggableTasks((tasks) => tasks.filter((item) => item.id !== args.event.id));
      setSortableTasks((tasks) => tasks.filter((item) => item.id !== args.event.id));
      setDragulaTasks((tasks) => tasks.filter((item) => item.id !== args.event.id));
      setToastMessage(args.event.title + ' added');
      setToastOpen(true);
    }
  }, []);

  const handleEventDeleted = useCallback((args: MbscEventDeletedEvent) => {
    setToastMessage(args.event.title + ' unscheduled');
    setToastOpen(true);
  }, []);

  const handleItemDrop = useCallback((args: MbscItemDragEvent) => {
    if (args.data) {
      setDraggableTasks((tasks) => [...tasks, args.data]);
    }
  }, []);

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

  useEffect(() => {
    let sortableInstance: Sortable;
    if (sortableCont) {
      sortableInstance = new Sortable(sortableCont, {
        animation: 150,
        forceFallback: true,
      });

      sortableJsDraggable.init(sortableInstance, {
        cloneSelector: '.sortable-drag',
        externalDrop: true,
        onExternalDrop: (a: MbscExternalDropEvent) => {
          const dragData = a.dragData;
          setSortableTasks((prev) => {
            const newTasks = [...prev];
            newTasks.splice(a.position, 0, dragData);
            return newTasks;
          });
        },
      });
    }

    let drake: Drake;
    if (dragulaCont) {
      drake = dragula([dragulaCont]);
      dragulaDraggable.init(drake, {
        externalDrop: true,
        onExternalDrop: (a: MbscExternalDropEvent) => {
          const dragData = a.dragData;
          setDragulaTasks((prev) => {
            const newTasks = [...prev];
            newTasks.splice(a.position, 0, dragData);
            return newTasks;
          });
        },
      });
    }

    return () => {
      if (sortableInstance) {
        sortableInstance.destroy();
      }
      if (drake) {
        drake.destroy();
      }
    };
  }, [dragulaCont, sortableCont]);

  useEffect(() => {
    getJson(
      'https://trial.mobiscroll.com/drag-drop-events/',
      (events) => {
        setEvents(events);
      },
      'jsonp',
    );
  }, []);

  return (
    <div className="mbsc-grid mbsc-no-padding mds-full-height">
      <div className="mbsc-row mds-full-height">
        <div className="mbsc-col-sm-9 mds-drag-drop-sort-calendar mds-full-height">
          <Eventcalendar
            clickToCreate={true}
            dragToCreate={true}
            dragToMove={true}
            dragToResize={true}
            eventDelete={true}
            data={myEvents}
            view={myView}
            externalDrop={true}
            dragToMove={true}
            dragToCreate={true}
            externalDrag={true}
            onEventCreated={handleEventCreated}
            onEventDeleted={handleEventDeleted}
          />
        </div>
        <div className="mbsc-col-sm-3 mds-drag-drop-sort-container-wrapper mds-full-height">
          <div className="mbsc-txt-muted mds-third-party-title">Mobiscroll draggable</div>
          <div className="mds-drag-drop-sort-container" ref={setDropCont}>
            <Dropcontainer onItemDrop={handleItemDrop} element={dropCont}>
              {myDraggableTasks.map((task) => (
                <Task key={task.id} data={task} isDraggable={true} />
              ))}
            </Dropcontainer>
          </div>
          <div className="mbsc-txt-muted mds-third-party-title">SortableJS list (externally sortable)</div>
          <div className="mds-drag-drop-sort-container" ref={setSortableCont}>
            {mySortableTasks.map((task) => (
              <Task key={task.id} data={task} />
            ))}
          </div>
          <div className="mbsc-txt-muted mds-third-party-title">Dragula list (externally sortable)</div>
          <div className="mds-drag-drop-sort-container" ref={setDragulaCont}>
            {myDragulaTasks.map((task) => (
              <Task key={task.id} data={task} />
            ))}
          </div>
        </div>
      </div>
      <Toast message={toastMessage} isOpen={isToastOpen} onClose={handleToastClose} />
    </div>
  );
};

export default App;
```

### CSS

```css
.mds-external-third-party-drop-calendar {
  border-right: 1px solid #ccc;
}

.mds-external-third-party-drag-container {
  margin: 10px;
}

.mds-external-third-party-task {
  margin: 10px;
  padding: 10px;
  touch-action: none;
  color: #fff;
  border-radius: 8px;
  font-family:
    -apple-system,
    Segoe UI,
    Roboto,
    sans-serif;
}
```

## Implementation notes

- Use `view: { scheduler: { type: 'week' } }`. Enable `externalDrop: true`, `externalDrag: true`, `dragToMove: true`, `dragToCreate: true`. Load events via `getJson` from a JSONP endpoint on mount.
- The sidebar has three separate task lists, each demonstrating a different integration path:
  - **Mobiscroll Draggable** (red, tasks 1-4): wrap each item with `Draggable` (Angular: `mbsc-draggable` directive, Vue: `MbscDraggable`) passing `dragData` set to the event object. Wrap the list in a `Dropcontainer` with `onItemDrop` to re-add events dragged back from the calendar.
  - **SortableJS** (orange, tasks 5-8): `new Sortable(container, { animation: 150, forceFallback: true })`; call `sortableJsDraggable.init(sortableInstance, { externalDrop: true, onExternalDrop })` to connect to the calendar and handle back-drops.
  - **Dragula** (green, tasks 9-12): `dragula([container])`; call `dragulaDraggable.init(drake, { externalDrop: true, onExternalDrop })`.
- On `onEventCreated`, if `args.action === 'externalDrop'`, remove the item by id from all three task arrays and show a toast with the event title + " added".
- On `onEventDeleted`, show a toast with the event title + " unscheduled".
- Each task list is its own state array; items disappear when dropped onto the calendar and reappear when dragged back via `onItemDrop` / `onExternalDrop`.

## 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: [Prevent Double Booking](https://demo.mobiscroll.com/react/scheduler/prevent-double-booking-events.md) - Use the React Scheduler to block event overlaps per event, per resource, or globally — with toast notifications when a conflict is blocked.
- Next: [External Task Presets](https://demo.mobiscroll.com/react/scheduler/external-event-presets.md) - Drag task templates from an external list onto the React Scheduler — schedule, assign a technician in a popup, and block drops on restricted slots.
