Removing event from fullcalendar with React by dragging out
Recently, I’ve been tasked with adding CRUD like interaction to fullcalendar component with react.
Despite @fullcalendar/interaction
plugin providing most of the functionality out of the box, it still misses a drag-out event but it is powerful enough to provide primitives for custom implementation.
Let’s see it in the wild by kicking off the tires with our component’s scaffold:
import FullCalendar from "@fullcalendar/react";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin, { EventDragStopArg } from "@fullcalendar/interaction";
import { DateSelectArg } from "@fullcalendar/core";
export default function App() {
const add = ({ start, end, view: { calendar } }: DateSelectArg): void => {
calendar.addEvent({ start, end });
};
const remove = ({ event, jsEvent, view: { calendar } }: EventDragStopArg): void => {
console.log("Remove me");
};
return (
<div>
<FullCalendar
plugins={[timeGridPlugin, interactionPlugin]}
allDaySlot={false}
initialView="timeGridWeek"
dragRevertDuration={0}
headerToolbar={false}
editable={true}
eventDragStop={remove}
eventOverlap={false}
select={add}
selectable={true}
selectOverlap={false}
themeSystem="bootstrap"
/>
</div>
);
}
This code creates FullCalendar
component with a time-week grid supporting new events upon selection. It also prevents overlapping on resizing and drag & drop (dragRevertDuration
set to 0, eliminates ghost-like effect when an event is dragged to invalid spot).
To implement our remove
handler, we need to look at EventDragStopArg
signature first:
interface EventDragStopArg {
el: HTMLElement
event: EventApi
jsEvent: MouseEvent
view: ViewApi
}
By having access to MouseEvent
object, we can calculate if the event’s pageX
and pageY
coordinates (where the dragging stops) are within of calendar’s boundaries. This will allow us to detect whether an event has been dragged out of the calendar.
To get calendar boundaries we first need to get a ref
of parent div
:
import { useRef } from 'react';
...
export default function App() {
const container = useRef(null);
return (
<div ref={container}>
...
</div>
);
}
Once we have access to the container, we can start implementing our remove
function:
if (!container.current) return;
if (inElement({ x: jsEvent.pageX, y: jsEvent.pageY }, container.current!)) return;
event.remove();
Now, we need to write inElement
function to check if {x,y}
are within the element’s boundaries:
function inElement(point: { x: number, y: number }, element: HTMLElement): boolean {
const rect = element.getBoundingClientRect();
const top = rect.top + window.scrollY;
const bottom = rect.bottom + window.scrollY;
const left = rect.left + window.scrollX;
const right = rect.right + window.scrollX;
return point.x >= left && point.x <= right && point.y >= top && point.y <= bottom;
}
With all changes in place, our final code looks as follows:
import { useRef } from "react";
import FullCalendar from "@fullcalendar/react";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin, { EventDragStopArg } from "@fullcalendar/interaction";
import { DateSelectArg } from "@fullcalendar/core";
export default function App() {
const container = useRef(null);
const add = ({ start, end, view: { calendar } }: DateSelectArg): void => {
calendar.addEvent({ start, end });
};
const remove = ({ event, jsEvent, view: { calendar } }: EventDragStopArg): void => {
if (!container.current) return;
if (inElement({ x: jsEvent.pageX, y: jsEvent.pageY }, container.current!)) return;
event.remove();
};
return (
<div ref={container}>
<FullCalendar
plugins={[timeGridPlugin, interactionPlugin]}
allDaySlot={false}
dragRevertDuration={0}
initialView="timeGridWeek"
headerToolbar={false}
editable={true}
eventOverlap={false}
eventDragStop={remove}
select={add}
selectable={true}
selectOverlap={false}
themeSystem="bootstrap"
/>
</div>
);
}
function inElement(point: { x: number; y: number }, element: HTMLElement): boolean {
const rect = element.getBoundingClientRect();
const top = rect.top + window.scrollY;
const bottom = rect.bottom + window.scrollY;
const left = rect.left + window.scrollX;
const right = rect.right + window.scrollX;
return point.x >= left && point.x <= right && point.y >= top && point.y <= bottom;
}
You can see a working demo on CodeSandbox.