Monitors

Monitors can listen to all events for a draggable entity, such as an element or file.

Monitors are a way of listening for drag and drop operation events anywhere.

Basic usage

// monitors are exposed through adapters
import { monitorForExternal } from '@atlaskit/pragmatic-drag-and-drop/external/adapter';

// listen for all drag events for files
const cleanup = monitorForExternal({
    onDragStart: () => console.log('I am called whenever any external enters this window'),
});

A monitor can be located anywhere and is not tied to any element.

// basic usage with react
import React, { useEffect, useRef } from 'react';
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';

function App() {
    // It is often convenient to tie monitors to components using effects
    // By doing this you can ensure you monitors are cleaned up
    // when a component is no longer being used.
    useEffect(() => {
        return monitorForElements({
            onDragStart: () => console.log('I am called whenever any element drag starts'),
        });
    }, []);

    return <div>{/*...*/}</div>;
}

Conditional monitoring

A monitor can be conditionally activated for a drag operation by using the canMonitor() function.

  • canMonitor?: (args: MonitorGetFeedbackArgs<DragType>) => boolean
type MonitorGetFeedbackArgs = {
    /**
     * The users initial drag location
     */
    initial: DragLocation;
    /**
     * The data associated with the entity being dragged
     */
    source: DragType['payload'];
};
monitorForElements({
    canMonitor: ({ source }) => source.data.type === 'card',
    onDragStart: () => console.log('I will only be activated when dragging a card!'),
});

canMonitor() is only called once for a monitor during a drag operation as the drag is starting. If a monitor is added during an active drag, then canMonitor() will be called as it is created with the intial drag source value and initial drag location. All monitors are called with the same MonitorGetFeedbackArgs value during the same drag operation. We wanted to make this behvaiour clear as the source value can change for external drag types in the "onDrop" event (eg files).

If you want to do more complicated checks, you can do those inside of your monitor event listeners:

monitorForElements({
    // filtering will opt the monitor into the whole drag operation
    canMonitor: ({ source }) => source.data.type === 'card',
    onDrag: ({ location }) => {
        // Additional action filtering based on changes during the drag
        if (location.current.data.action === 'delete') {
            console.log('about to delete');
        }
    },
});

Event ordering

All events flow through the system in the same way:

  1. drag source (eg draggable) if relevant
  2. drop targets - inner most upwards (bubble ordering) grandChild -> child -> parent
  3. monitors (in the order that they were created)

monitors are called in the same order in which they are created. Over time it can be hard to reason about what this ordering is as a consumer as you might be creating / destroying monitors frequently. So it is safe to expect that "monitors are called last", but you need to be careful if you want to rely on any ordering relationships between monitors.

function App() {
    useEffect(() => {
        const cleanup = monitorForElements({
            onDragStart: () => 'This monitor gets created on each render!',
        });
        return cleanup;
    });

    return null;
}

Learn more about event ordering

Adding monitors during an event

A monitor that is added during an event (eg onDragStart) will not be called for the current event. This is to prevent the accidental creation of infinite loops. This behaviour matches native EventTargets where an event listener cannot add another event listener during an active event to the same event target in the same event phase.

const monitor1 = monitorForElements({
    onGenerateDragPreview: () => {
        const monitor2 = monitorForElements({
            // Setting up another monitor during `onGenerateDragPreview`
            onGenerateDragPreview: () => {
                // monitor2 will not be called for the _current_ `onGenerateDragPreview` event
            },
            // The monitor will be called for subsequent events
            onDragStart: () => {},
        });
    },
});

Was this page helpful?
We use this feedback to improve our documentation.